KVM Hypervisor Cleanup
[ganeti-local] / lib / hypervisor / hv_kvm.py
1 #
2 #
3
4 # Copyright (C) 2008 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """KVM hypervisor
23
24 """
25
26 import os
27 import os.path
28 import re
29 import tempfile
30 from cStringIO import StringIO
31
32 from ganeti import utils
33 from ganeti import constants
34 from ganeti import errors
35 from ganeti.hypervisor import hv_base
36
37
38 class KVMHypervisor(hv_base.BaseHypervisor):
39   """Fake hypervisor interface.
40
41   This can be used for testing the ganeti code without having to have
42   a real virtualisation software installed.
43
44   """
45   _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
46   _PIDS_DIR = _ROOT_DIR + "/pid"
47   _CTRL_DIR = _ROOT_DIR + "/ctrl"
48   _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR]
49
50   def __init__(self):
51     hv_base.BaseHypervisor.__init__(self)
52     # Let's make sure the directories we need exist, even if the RUN_DIR lives
53     # in a tmpfs filesystem or has been otherwise wiped out.
54     for dir in self._DIRS:
55       if not os.path.exists(dir):
56         os.mkdir(dir)
57
58   def _WriteNetScript(self, instance, seq, nic):
59     """Write a script to connect a net interface to the proper bridge.
60
61     This can be used by any qemu-type hypervisor.
62
63     @param instance: instance we're acting on
64     @type instance: instance object
65     @param seq: nic sequence number
66     @type seq: int
67     @param nic: nic we're acting on
68     @type nic: nic object
69     @return: netscript file name
70     @rtype: string
71
72     """
73     script = StringIO()
74     script.write("#!/bin/sh\n")
75     script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
76     script.write("export INSTANCE=%s\n" % instance.name)
77     script.write("export MAC=%s\n" % nic.mac)
78     script.write("export IP=%s\n" % nic.ip)
79     script.write("export BRIDGE=%s\n" % nic.bridge)
80     script.write("export INTERFACE=$1\n")
81     # TODO: make this configurable at ./configure time
82     script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
83     script.write("  # Execute the user-specific vif file\n")
84     script.write("  /etc/ganeti/kvm-vif-bridge\n")
85     script.write("else\n")
86     script.write("  # Connect the interface to the bridge\n")
87     script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
88     script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
89     script.write("fi\n\n")
90     # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
91     # mounted noexec sometimes, so we'll have to find another place.
92     (tmpfd, tmpfile_name) = tempfile.mkstemp()
93     tmpfile = os.fdopen(tmpfd, 'w')
94     tmpfile.write(script.getvalue())
95     tmpfile.close()
96     os.chmod(tmpfile_name, 0755)
97     return tmpfile_name
98
99   def ListInstances(self):
100     """Get the list of running instances.
101
102     We can do this by listing our live instances directory and checking whether
103     the associated kvm process is still alive.
104
105     """
106     result = []
107     for name in os.listdir(self._PIDS_DIR):
108       file = "%s/%s" % (self._PIDS_DIR, name)
109       if utils.IsProcessAlive(utils.ReadPidFile(file)):
110         result.append(name)
111     return result
112
113   def GetInstanceInfo(self, instance_name):
114     """Get instance properties.
115
116     Args:
117       instance_name: the instance name
118
119     Returns:
120       (name, id, memory, vcpus, stat, times)
121     """
122     pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
123     pid = utils.ReadPidFile(pidfile)
124     if not utils.IsProcessAlive(pid):
125       return None
126
127     cmdline_file = "/proc/%s/cmdline" % pid
128     try:
129       fh = open(cmdline_file, 'r')
130       try:
131         cmdline = fh.read()
132       finally:
133         fh.close()
134     except IOError, err:
135       raise errors.HypervisorError("Failed to list instance %s: %s" %
136                                    (instance_name, err))
137
138     memory = 0
139     vcpus = 0
140     stat = "---b-"
141     times = "0"
142
143     arg_list = cmdline.split('\x00')
144     while arg_list:
145       arg =  arg_list.pop(0)
146       if arg == '-m':
147         memory = arg_list.pop(0)
148       elif arg == '-smp':
149         vcpus = arg_list.pop(0)
150
151     return (instance_name, pid, memory, vcpus, stat, times)
152
153   def GetAllInstancesInfo(self):
154     """Get properties of all instances.
155
156     Returns:
157       [(name, id, memory, vcpus, stat, times),...]
158     """
159     data = []
160     for name in os.listdir(self._PIDS_DIR):
161       file = "%s/%s" % (self._PIDS_DIR, name)
162       if utils.IsProcessAlive(utils.ReadPidFile(file)):
163         data.append(self.GetInstanceInfo(name))
164
165     return data
166
167   def StartInstance(self, instance, block_devices, extra_args):
168     """Start an instance.
169
170     """
171     temp_files = []
172     pidfile = self._PIDS_DIR + "/%s" % instance.name
173     if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
174       raise errors.HypervisorError("Failed to start instance %s: %s" %
175                                    (instance.name, "already running"))
176
177     kvm = constants.KVM_PATH
178     kvm_cmd = [kvm]
179     kvm_cmd.extend(['-m', instance.memory])
180     kvm_cmd.extend(['-smp', instance.vcpus])
181     kvm_cmd.extend(['-pidfile', pidfile])
182     # used just by the vnc server, if enabled
183     kvm_cmd.extend(['-name', instance.name])
184     kvm_cmd.extend(['-daemonize'])
185     if not instance.hvm_acpi:
186       kvm_cmd.extend(['-no-acpi'])
187     if not instance.nics:
188       kvm_cmd.extend(['-net', 'none'])
189     else:
190       nic_seq = 0
191       for nic in instance.nics:
192         script = self._WriteNetScript(instance, nic_seq, nic)
193         # FIXME: handle other models
194         nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
195         kvm_cmd.extend(['-net', nic_val])
196         kvm_cmd.extend(['-net', 'tap,script=%s' % script])
197         temp_files.append(script)
198         nic_seq += 1
199
200     boot_drive = True
201     for cfdev, rldev in block_devices:
202       # TODO: handle FD_LOOP and FD_BLKTAP (?)
203       if boot_drive:
204         boot_val = ',boot=on'
205         boot_drive = False
206       else:
207         boot_val = ''
208
209       # TODO: handle different if= types
210       if_val = ',if=virtio'
211
212       drive_val = 'file=%s,format=raw%s%s' % (rldev.dev_path, if_val, boot_val)
213       kvm_cmd.extend(['-drive', drive_val])
214
215     # kernel handling
216     if instance.kernel_path in (None, constants.VALUE_DEFAULT):
217       kpath = constants.XEN_KERNEL # FIXME: other name??
218     else:
219       if not os.path.exists(instance.kernel_path):
220         raise errors.HypervisorError("The kernel %s for instance %s is"
221                                      " missing" % (instance.kernel_path,
222                                                    instance.name))
223       kpath = instance.kernel_path
224
225     kvm_cmd.extend(['-kernel', kpath])
226
227     # initrd handling
228     if instance.initrd_path in (None, constants.VALUE_DEFAULT):
229       if os.path.exists(constants.XEN_INITRD):
230         initrd_path = constants.XEN_INITRD
231       else:
232         initrd_path = None
233     elif instance.initrd_path == constants.VALUE_NONE:
234       initrd_path = None
235     else:
236       if not os.path.exists(instance.initrd_path):
237         raise errors.HypervisorError("The initrd %s for instance %s is"
238                                      " missing" % (instance.initrd_path,
239                                                    instance.name))
240       initrd_path = instance.initrd_path
241
242     if initrd_path:
243       kvm_cmd.extend(['-initrd', initrd_path])
244
245     kvm_cmd.extend(['-append', 'console=ttyS0,38400 root=/dev/vda'])
246
247     #"hvm_boot_order",
248     #"hvm_cdrom_image_path",
249
250     kvm_cmd.extend(['-nographic'])
251     # FIXME: handle vnc, if needed
252     # How do we decide whether to have it or not?? :(
253     #"vnc_bind_address",
254     #"network_port"
255     base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
256     monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
257     kvm_cmd.extend(['-monitor', monitor_dev])
258     serial_dev = 'unix:%s.serial,server,nowait' % base_control
259     kvm_cmd.extend(['-serial', serial_dev])
260
261     result = utils.RunCmd(kvm_cmd)
262     if result.failed:
263       raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
264                                    (instance.name, result.fail_reason,
265                                     result.output))
266
267     if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
268       raise errors.HypervisorError("Failed to start instance %s: %s" %
269                                    (instance.name))
270
271     for file in temp_files:
272       utils.RemoveFile(file)
273
274   def StopInstance(self, instance, force=False):
275     """Stop an instance.
276
277     """
278     pid_file = self._PIDS_DIR + "/%s" % instance.name
279     pid = utils.ReadPidFile(pid_file)
280     if pid > 0 and utils.IsProcessAlive(pid):
281       if force or not instance.hvm_acpi:
282         utils.KillProcess(pid)
283       else:
284         # This only works if the instance os has acpi support
285         monitor_socket = '%s/%s.monitor'  % (self._CTRL_DIR, instance.name)
286         socat = 'socat -u STDIN UNIX-CONNECT:%s' % monitor_socket
287         command = "echo 'system_powerdown' | %s" % socat
288         result = utils.RunCmd(command)
289         if result.failed:
290           raise errors.HypervisorError("Failed to stop instance %s: %s" %
291                                        (instance.name, result.fail_reason))
292
293     if not utils.IsProcessAlive(pid):
294       utils.RemoveFile(pid_file)
295
296   def RebootInstance(self, instance):
297     """Reboot an instance.
298
299     """
300     # For some reason if we do a 'send-key ctrl-alt-delete' to the control
301     # socket the instance will stop, but now power up again. So we'll resort
302     # to shutdown and restart.
303     self.StopInstance(instance)
304     self.StartInstance(instance)
305
306   def GetNodeInfo(self):
307     """Return information about the node.
308
309     The return value is a dict, which has to have the following items:
310       (all values in MiB)
311       - memory_total: the total memory size on the node
312       - memory_free: the available memory on the node for instances
313       - memory_dom0: the memory used by the node itself, if available
314
315     """
316     # global ram usage from the xm info command
317     # memory                 : 3583
318     # free_memory            : 747
319     # note: in xen 3, memory has changed to total_memory
320     try:
321       fh = file("/proc/meminfo")
322       try:
323         data = fh.readlines()
324       finally:
325         fh.close()
326     except IOError, err:
327       raise errors.HypervisorError("Failed to list node info: %s" % err)
328
329     result = {}
330     sum_free = 0
331     for line in data:
332       splitfields = line.split(":", 1)
333
334       if len(splitfields) > 1:
335         key = splitfields[0].strip()
336         val = splitfields[1].strip()
337         if key == 'MemTotal':
338           result['memory_total'] = int(val.split()[0])/1024
339         elif key in ('MemFree', 'Buffers', 'Cached'):
340           sum_free += int(val.split()[0])/1024
341         elif key == 'Active':
342           result['memory_dom0'] = int(val.split()[0])/1024
343     result['memory_free'] = sum_free
344
345     cpu_total = 0
346     try:
347       fh = open("/proc/cpuinfo")
348       try:
349         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
350                                    fh.read()))
351       finally:
352         fh.close()
353     except EnvironmentError, err:
354       raise errors.HypervisorError("Failed to list node info: %s" % err)
355     result['cpu_total'] = cpu_total
356
357     return result
358
359   @staticmethod
360   def GetShellCommandForConsole(instance):
361     """Return a command for connecting to the console of an instance.
362
363     """
364     # TODO: we can either try the serial socket or suggest vnc
365     return "echo Console not available for the kvm hypervisor yet"
366
367   def Verify(self):
368     """Verify the hypervisor.
369
370     Check that the binary exists.
371
372     """
373     if not os.path.exists(constants.KVM_PATH):
374       return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
375