Merge remote-tracking branch 'origin/master' into staging
[mirror/dsa-puppet.git] / modules / ganeti2 / files / ganeti-machined-register-instances
1 #!/usr/bin/python
2 #
3 # Register ganeti KVM instances with systemd-machined
4 #
5 # Copyright (c) 2015 Skroutz S.A.
6 #
7 # All rights reserved.
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are
11 # met:
12 #
13 # * Redistributions of source code must retain the above copyright
14 #   notice, this list of conditions and the following disclaimer.
15 #
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.
19 #
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.
31
32 """Register Ganeti KVM instances with systemd-machined
33
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.
37
38 All instances are placed in ganeti.slice, and a transient scope unit is created
39 for each instance via machined.
40
41 Instance UUIDs are obtained from the KVM command contained in the instance
42 runtime.
43
44 """
45
46 import os
47 import sys
48 import socket
49
50 import dbus
51 from uuid import UUID
52
53 # Ganeti private module location
54 sys.path.append('/usr/share/ganeti/default')
55
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
61
62
63 MACHINED_MANAGER = 'org.freedesktop.machine1.Manager'
64
65
66 class InstanceNotRunning(Exception):
67     """Instance not running error"""
68     pass
69
70
71 def get_kvm_instance_pid(name):
72     """Get a KVM instance PID"""
73     kvm = GetHypervisor(HT_KVM)
74
75     # pylint: disable=protected-access
76     _, pid, alive = kvm._InstancePidAlive(name)
77
78     if not alive:
79         raise InstanceNotRunning
80
81     return pid
82
83
84 def get_kvm_instance_uuid(name):
85     """Get a KVM instance UUID from the runtime
86
87     Since the UUID is not available via ssconf, this function figures it out
88     from the runtime's command arguments.
89     """
90     kvm = GetHypervisor(HT_KVM)
91
92     # pylint: disable=protected-access
93     cmd = load_runtime(kvm._ReadKVMRuntime(name))[0]
94
95     # Find '-uuid' and get its parameter
96     idx = cmd.index('-uuid')
97     if idx >= 0:
98         return UUID(cmd[idx + 1])
99
100     return None
101
102
103 def register_instance(name, uuid, pid):
104     """Register a single instance.
105
106     This makes sense only for KVM and LXC instances currently
107     """
108     bus = dbus.SystemBus()
109     machined = bus.get_object('org.freedesktop.machine1',
110                               '/org/freedesktop/machine1')
111
112     if isinstance(uuid, UUID):
113         _uuid = dbus.ByteArray(uuid.bytes)
114     else:
115         _uuid = []
116
117     machined.CreateMachine(name,
118                            _uuid,
119                            "ganeti",
120                            "vm",
121                            pid,
122                            "",
123                            [("Slice", "ganeti.slice"),
124                             ("Description", "%s started via Ganeti" % name)],
125                            dbus_interface=MACHINED_MANAGER)
126
127
128 def register_all_kvm_instances():
129     """Register all KVM instances not already registered"""
130     conf = SimpleStore()
131     try:
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")
137         sys.exit(1)
138
139     if not HT_KVM in enabled_hvs:
140         sys.exit(0)
141
142     kvm = GetHypervisor(HT_KVM)
143
144     bus = dbus.SystemBus()
145     machined = bus.get_object('org.freedesktop.machine1',
146                               '/org/freedesktop/machine1')
147
148     registered = [str(x[0]) for x in
149                   machined.ListMachines(dbus_interface=MACHINED_MANAGER)]
150
151     for instance in kvm.ListInstances():
152         if instance in registered:
153             print "Skipping %s, already registered" % instance
154             continue
155
156         try:
157             pid = get_kvm_instance_pid(instance)
158         except InstanceNotRunning:
159             print "Skipping %s, not running" % instance
160             continue
161
162         uuid = get_kvm_instance_uuid(instance)
163         print "Registering %s (%s, PID: %d)" % (instance, uuid, pid)
164         register_instance(instance, uuid, pid)
165
166
167 def main():
168     """Register a single instance or all instances
169
170     When run as a hook, register the instance the hooks is about. Otherwise,
171     register all running instances.
172
173     """
174
175     # Exit if PID 1 is not systemd
176     if not os.path.isdir("/run/systemd/system"):
177         sys.exit(0)
178
179     # Are we being called as a hook?
180     opcode = os.environ.get('GANETI_OP_CODE')
181     if opcode is None:
182         # No, let us just register all instances
183         register_all_kvm_instances()
184         sys.exit(0)
185
186     if os.environ['GANETI_HOOKS_PHASE'] != "post":
187         sys.exit(0)
188
189     if os.environ['GANETI_HOOKS_VERSION'] != "2":
190         print "Invalid hooks version: %s" % os.environ['GANETI_HOOKS_VERSION']
191         sys.exit(0)
192
193     if os.environ['GANETI_INSTANCE_HYPERVISOR'] != HT_KVM:
194         sys.exit(0)
195
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']
201     else:
202         print "Don't know how to handle %s" % opcode
203         sys.exit(0)
204
205     if primary != socket.getfqdn():
206         sys.exit(0)
207
208     name = os.environ['GANETI_INSTANCE_NAME']
209     try:
210         pid = get_kvm_instance_pid(name)
211     except InstanceNotRunning:
212         sys.exit(0)
213     uuid = get_kvm_instance_uuid(name)
214     register_instance(name, uuid, pid)
215     print "Registered %s with systemd-machined" % name
216
217
218 if __name__ == "__main__":
219     main()