--- /dev/null
+#!/usr/bin/python
+#
+# Register ganeti KVM instances with systemd-machined
+#
+# Copyright (c) 2015 Skroutz S.A.
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Register Ganeti KVM instances with systemd-machined
+
+This script is meant to be run as a hook or directly. When run as a hook, it
+will register a newly-created, started, rebooted, migrated or failed-over KVM
+instance with systemd-machined.
+
+All instances are placed in ganeti.slice, and a transient scope unit is created
+for each instance via machined.
+
+Instance UUIDs are obtained from the KVM command contained in the instance
+runtime.
+
+"""
+
+import os
+import sys
+import socket
+
+import dbus
+from uuid import UUID
+
+# Ganeti private module location
+sys.path.append('/usr/share/ganeti/default')
+
+from ganeti.ssconf import SimpleStore
+from ganeti.hypervisor import GetHypervisor
+from ganeti.hypervisor.hv_kvm import _AnalyzeSerializedRuntime as load_runtime
+from ganeti.constants import HT_KVM
+from ganeti.errors import ConfigurationError
+
+
+MACHINED_MANAGER = 'org.freedesktop.machine1.Manager'
+
+
+class InstanceNotRunning(Exception):
+ """Instance not running error"""
+ pass
+
+
+def get_kvm_instance_pid(name):
+ """Get a KVM instance PID"""
+ kvm = GetHypervisor(HT_KVM)
+
+ # pylint: disable=protected-access
+ _, pid, alive = kvm._InstancePidAlive(name)
+
+ if not alive:
+ raise InstanceNotRunning
+
+ return pid
+
+
+def get_kvm_instance_uuid(name):
+ """Get a KVM instance UUID from the runtime
+
+ Since the UUID is not available via ssconf, this function figures it out
+ from the runtime's command arguments.
+ """
+ kvm = GetHypervisor(HT_KVM)
+
+ # pylint: disable=protected-access
+ cmd = load_runtime(kvm._ReadKVMRuntime(name))[0]
+
+ # Find '-uuid' and get its parameter
+ idx = cmd.index('-uuid')
+ if idx >= 0:
+ return UUID(cmd[idx + 1])
+
+ return None
+
+
+def register_instance(name, uuid, pid):
+ """Register a single instance.
+
+ This makes sense only for KVM and LXC instances currently
+ """
+ bus = dbus.SystemBus()
+ machined = bus.get_object('org.freedesktop.machine1',
+ '/org/freedesktop/machine1')
+
+ if isinstance(uuid, UUID):
+ _uuid = dbus.ByteArray(uuid.bytes)
+ else:
+ _uuid = []
+
+ machined.CreateMachine(name,
+ _uuid,
+ "ganeti",
+ "vm",
+ pid,
+ "",
+ [("Slice", "ganeti.slice"),
+ ("Description", "%s started via Ganeti" % name)],
+ dbus_interface=MACHINED_MANAGER)
+
+
+def register_all_kvm_instances():
+ """Register all KVM instances not already registered"""
+ conf = SimpleStore()
+ try:
+ enabled_hvs = conf.GetHypervisorList()
+ except ConfigurationError:
+ # Cluster is probably not initialized
+ sys.stderr.write("E: failed to get hypervisor list"
+ " (not part of a cluster?)\n")
+ sys.exit(1)
+
+ if not HT_KVM in enabled_hvs:
+ sys.exit(0)
+
+ kvm = GetHypervisor(HT_KVM)
+
+ bus = dbus.SystemBus()
+ machined = bus.get_object('org.freedesktop.machine1',
+ '/org/freedesktop/machine1')
+
+ registered = [str(x[0]) for x in
+ machined.ListMachines(dbus_interface=MACHINED_MANAGER)]
+
+ for instance in kvm.ListInstances():
+ if instance in registered:
+ print "Skipping %s, already registered" % instance
+ continue
+
+ try:
+ pid = get_kvm_instance_pid(instance)
+ except InstanceNotRunning:
+ print "Skipping %s, not running" % instance
+ continue
+
+ uuid = get_kvm_instance_uuid(instance)
+ print "Registering %s (%s, PID: %d)" % (instance, uuid, pid)
+ register_instance(instance, uuid, pid)
+
+
+def main():
+ """Register a single instance or all instances
+
+ When run as a hook, register the instance the hooks is about. Otherwise,
+ register all running instances.
+
+ """
+
+ # Exit if PID 1 is not systemd
+ if not os.path.isdir("/run/systemd/system"):
+ sys.exit(0)
+
+ # Are we being called as a hook?
+ opcode = os.environ.get('GANETI_OP_CODE')
+ if opcode is None:
+ # No, let us just register all instances
+ register_all_kvm_instances()
+ sys.exit(0)
+
+ if os.environ['GANETI_HOOKS_PHASE'] != "post":
+ sys.exit(0)
+
+ if os.environ['GANETI_HOOKS_VERSION'] != "2":
+ print "Invalid hooks version: %s" % os.environ['GANETI_HOOKS_VERSION']
+ sys.exit(0)
+
+ if os.environ['GANETI_INSTANCE_HYPERVISOR'] != HT_KVM:
+ sys.exit(0)
+
+ if opcode in ('OP_INSTANCE_CREATE', 'OP_INSTANCE_STARTUP',
+ 'OP_INSTANCE_REBOOT'):
+ primary = os.environ['GANETI_INSTANCE_PRIMARY']
+ elif opcode in ('OP_INSTANCE_MIGRATE', 'OP_INSTANCE_FAILOVER'):
+ primary = os.environ['GANETI_NEW_PRIMARY']
+ else:
+ print "Don't know how to handle %s" % opcode
+ sys.exit(0)
+
+ if primary != socket.getfqdn():
+ sys.exit(0)
+
+ name = os.environ['GANETI_INSTANCE_NAME']
+ try:
+ pid = get_kvm_instance_pid(name)
+ except InstanceNotRunning:
+ sys.exit(0)
+ uuid = get_kvm_instance_uuid(name)
+ register_instance(name, uuid, pid)
+ print "Registered %s with systemd-machined" % name
+
+
+if __name__ == "__main__":
+ main()