3 # Register ganeti KVM instances with systemd-machined
5 # Copyright (c) 2015 Skroutz S.A.
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are
13 # * Redistributions of source code must retain the above copyright
14 # notice, this list of conditions and the following disclaimer.
16 # * Redistributions in binary form must reproduce the above copyright
17 # notice, this list of conditions and the following disclaimer in the
18 # documentation and/or other materials provided with the distribution.
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 """Register Ganeti KVM instances with systemd-machined
34 This script is meant to be run as a hook or directly. When run as a hook, it
35 will register a newly-created, started, rebooted, migrated or failed-over KVM
36 instance with systemd-machined.
38 All instances are placed in ganeti.slice, and a transient scope unit is created
39 for each instance via machined.
41 Instance UUIDs are obtained from the KVM command contained in the instance
53 # Ganeti private module location
54 sys.path.append('/usr/share/ganeti/default')
56 from ganeti.ssconf import SimpleStore
57 from ganeti.hypervisor import GetHypervisor
58 from ganeti.hypervisor.hv_kvm import _AnalyzeSerializedRuntime as load_runtime
59 from ganeti.constants import HT_KVM
60 from ganeti.errors import ConfigurationError
63 MACHINED_MANAGER = 'org.freedesktop.machine1.Manager'
66 class InstanceNotRunning(Exception):
67 """Instance not running error"""
71 def get_kvm_instance_pid(name):
72 """Get a KVM instance PID"""
73 kvm = GetHypervisor(HT_KVM)
75 # pylint: disable=protected-access
76 _, pid, alive = kvm._InstancePidAlive(name)
79 raise InstanceNotRunning
84 def get_kvm_instance_uuid(name):
85 """Get a KVM instance UUID from the runtime
87 Since the UUID is not available via ssconf, this function figures it out
88 from the runtime's command arguments.
90 kvm = GetHypervisor(HT_KVM)
92 # pylint: disable=protected-access
93 cmd = load_runtime(kvm._ReadKVMRuntime(name))[0]
95 # Find '-uuid' and get its parameter
96 idx = cmd.index('-uuid')
98 return UUID(cmd[idx + 1])
103 def register_instance(name, uuid, pid):
104 """Register a single instance.
106 This makes sense only for KVM and LXC instances currently
108 bus = dbus.SystemBus()
109 machined = bus.get_object('org.freedesktop.machine1',
110 '/org/freedesktop/machine1')
112 if isinstance(uuid, UUID):
113 _uuid = dbus.ByteArray(uuid.bytes)
117 machined.CreateMachine(name,
123 [("Slice", "ganeti.slice"),
124 ("Description", "%s started via Ganeti" % name)],
125 dbus_interface=MACHINED_MANAGER)
128 def register_all_kvm_instances():
129 """Register all KVM instances not already registered"""
132 enabled_hvs = conf.GetHypervisorList()
133 except ConfigurationError:
134 # Cluster is probably not initialized
135 sys.stderr.write("E: failed to get hypervisor list"
136 " (not part of a cluster?)\n")
139 if not HT_KVM in enabled_hvs:
142 kvm = GetHypervisor(HT_KVM)
144 bus = dbus.SystemBus()
145 machined = bus.get_object('org.freedesktop.machine1',
146 '/org/freedesktop/machine1')
148 registered = [str(x[0]) for x in
149 machined.ListMachines(dbus_interface=MACHINED_MANAGER)]
151 for instance in kvm.ListInstances():
152 if instance in registered:
153 print "Skipping %s, already registered" % instance
157 pid = get_kvm_instance_pid(instance)
158 except InstanceNotRunning:
159 print "Skipping %s, not running" % instance
162 uuid = get_kvm_instance_uuid(instance)
163 print "Registering %s (%s, PID: %d)" % (instance, uuid, pid)
164 register_instance(instance, uuid, pid)
168 """Register a single instance or all instances
170 When run as a hook, register the instance the hooks is about. Otherwise,
171 register all running instances.
175 # Exit if PID 1 is not systemd
176 if not os.path.isdir("/run/systemd/system"):
179 # Are we being called as a hook?
180 opcode = os.environ.get('GANETI_OP_CODE')
182 # No, let us just register all instances
183 register_all_kvm_instances()
186 if os.environ['GANETI_HOOKS_PHASE'] != "post":
189 if os.environ['GANETI_HOOKS_VERSION'] != "2":
190 print "Invalid hooks version: %s" % os.environ['GANETI_HOOKS_VERSION']
193 if os.environ['GANETI_INSTANCE_HYPERVISOR'] != HT_KVM:
196 if opcode in ('OP_INSTANCE_CREATE', 'OP_INSTANCE_STARTUP',
197 'OP_INSTANCE_REBOOT'):
198 primary = os.environ['GANETI_INSTANCE_PRIMARY']
199 elif opcode in ('OP_INSTANCE_MIGRATE', 'OP_INSTANCE_FAILOVER'):
200 primary = os.environ['GANETI_NEW_PRIMARY']
202 print "Don't know how to handle %s" % opcode
205 if primary != socket.getfqdn():
208 name = os.environ['GANETI_INSTANCE_NAME']
210 pid = get_kvm_instance_pid(name)
211 except InstanceNotRunning:
213 uuid = get_kvm_instance_uuid(name)
214 register_instance(name, uuid, pid)
215 print "Registered %s with systemd-machined" % name
218 if __name__ == "__main__":