From 20c7124536cc621b4610df60226d998eb4828e09 Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Mon, 5 Feb 2018 14:34:57 +0100 Subject: [PATCH] Put ganeti VMs into their own systemd scope --- .../files/ganeti-machined-register-instances | 219 ++++++++++++++++++ modules/ganeti2/manifests/init.pp | 26 +++ 2 files changed, 245 insertions(+) create mode 100755 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 index 000000000..6ca5158aa --- /dev/null +++ b/modules/ganeti2/files/ganeti-machined-register-instances @@ -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() diff --git a/modules/ganeti2/manifests/init.pp b/modules/ganeti2/manifests/init.pp index 597c1cc6a..a944f3db0 100644 --- a/modules/ganeti2/manifests/init.pp +++ b/modules/ganeti2/manifests/init.pp @@ -43,4 +43,30 @@ class ganeti2 { | EOF } } + + package { ['python-dbus', 'systemd-container']: ensure => installed } + file { '/usr/local/sbin/ganeti-machined-register-instances': + source => 'puppet:///modules/ganeti2/ganeti-machined-register-instances', + mode => '0555', + } + file { [ + '/etc/ganeti/hooks', + '/etc/ganeti/hooks/instance-reboot-post.d', + '/etc/ganeti/hooks/instance-migrate-post.d', + '/etc/ganeti/hooks/instance-start-post.d', + '/etc/ganeti/hooks/instance-failover-post.d', + '/etc/ganeti/hooks/instance-add-post.d', + ]: + ensure => directory, + } + file { [ + '/etc/ganeti/hooks/instance-reboot-post.d/00-ganeti-machined-register-instances', + '/etc/ganeti/hooks/instance-migrate-post.d/00-ganeti-machined-register-instances', + '/etc/ganeti/hooks/instance-start-post.d/00-ganeti-machined-register-instances', + '/etc/ganeti/hooks/instance-failover-post.d/00-ganeti-machined-register-instances', + '/etc/ganeti/hooks/instance-add-post.d/00-ganeti-machined-register-instances', + ]: + ensure => link, + target => '/usr/local/sbin/ganeti-machined-register-instances', + } } -- 2.20.1