Merge remote-tracking branch 'origin/master' into staging
[mirror/dsa-puppet.git] / modules / ganeti2 / files / ganeti-machined-register-instances
diff --git a/modules/ganeti2/files/ganeti-machined-register-instances b/modules/ganeti2/files/ganeti-machined-register-instances
new file mode 100755 (executable)
index 0000000..6ca5158
--- /dev/null
@@ -0,0 +1,219 @@
+#!/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()