Move the runas user at execution time
[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 import time
31 import logging
32 import pwd
33 from cStringIO import StringIO
34
35 from ganeti import utils
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import serializer
39 from ganeti import objects
40 from ganeti.hypervisor import hv_base
41
42
43 class KVMHypervisor(hv_base.BaseHypervisor):
44   """KVM hypervisor interface"""
45
46   _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
47   _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
48   _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
49   _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
50   _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
51
52   PARAMETERS = {
53     constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
54     constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
55     constants.HV_ROOT_PATH: hv_base.NO_CHECK,
56     constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
57     constants.HV_ACPI: hv_base.NO_CHECK,
58     constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
59     constants.HV_VNC_BIND_ADDRESS:
60       (False, lambda x: (utils.IsValidIP(x) or utils.IsNormAbsPath(x)),
61        "the VNC bind address must be either a valid IP address or an absolute"
62        " pathname", None, None),
63     constants.HV_VNC_TLS: hv_base.NO_CHECK,
64     constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
65     constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
66     constants.HV_VNC_PASSWORD_FILE: hv_base.OPT_FILE_CHECK,
67     constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
68     constants.HV_BOOT_ORDER:
69       hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
70     constants.HV_NIC_TYPE:
71       hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
72     constants.HV_DISK_TYPE:
73       hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
74     constants.HV_USB_MOUSE:
75       hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
76     constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
77     constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
78     constants.HV_DISK_CACHE:
79       hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES),
80     constants.HV_SECURITY_MODEL:
81       hv_base.ParamInSet(True, constants.HT_KVM_VALID_SM_TYPES),
82     constants.HV_SECURITY_DOMAIN: hv_base.NO_CHECK,
83     }
84
85   _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
86                                     re.M | re.I)
87   _MIGRATION_INFO_MAX_BAD_ANSWERS = 5
88   _MIGRATION_INFO_RETRY_DELAY = 2
89
90   _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
91
92   ANCILLARY_FILES = [
93     _KVM_NETWORK_SCRIPT,
94     ]
95
96   def __init__(self):
97     hv_base.BaseHypervisor.__init__(self)
98     # Let's make sure the directories we need exist, even if the RUN_DIR lives
99     # in a tmpfs filesystem or has been otherwise wiped out.
100     dirs = [(dname, constants.RUN_DIRS_MODE) for dname in self._DIRS]
101     utils.EnsureDirs(dirs)
102
103   @classmethod
104   def _InstancePidFile(cls, instance_name):
105     """Returns the instance pidfile.
106
107     """
108     return utils.PathJoin(cls._PIDS_DIR, instance_name)
109
110   @classmethod
111   def _InstancePidInfo(cls, pid):
112     """Check pid file for instance information.
113
114     Check that a pid file is associated with an instance, and retrieve
115     information from its command line.
116
117     @type pid: string or int
118     @param pid: process id of the instance to check
119     @rtype: tuple
120     @return: (instance_name, memory, vcpus)
121     @raise errors.HypervisorError: when an instance cannot be found
122
123     """
124     alive = utils.IsProcessAlive(pid)
125     if not alive:
126       raise errors.HypervisorError("Cannot get info for pid %s" % pid)
127
128     cmdline_file = utils.PathJoin("/proc", str(pid), "cmdline")
129     try:
130       cmdline = utils.ReadFile(cmdline_file)
131     except EnvironmentError, err:
132       raise errors.HypervisorError("Can't open cmdline file for pid %s: %s" %
133                                    (pid, err))
134
135     instance = None
136     memory = 0
137     vcpus = 0
138
139     arg_list = cmdline.split('\x00')
140     while arg_list:
141       arg =  arg_list.pop(0)
142       if arg == "-name":
143         instance = arg_list.pop(0)
144       elif arg == "-m":
145         memory = int(arg_list.pop(0))
146       elif arg == "-smp":
147         vcpus = int(arg_list.pop(0))
148
149     if instance is None:
150       raise errors.HypervisorError("Pid %s doesn't contain a ganeti kvm"
151                                    " instance" % pid)
152
153     return (instance, memory, vcpus)
154
155   def _InstancePidAlive(self, instance_name):
156     """Returns the instance pidfile, pid, and liveness.
157
158     @type instance_name: string
159     @param instance_name: instance name
160     @rtype: tuple
161     @return: (pid file name, pid, liveness)
162
163     """
164     pidfile = self._InstancePidFile(instance_name)
165     pid = utils.ReadPidFile(pidfile)
166
167     alive = False
168     try:
169       cmd_instance = self._InstancePidInfo(pid)[0]
170       alive = (cmd_instance == instance_name)
171     except errors.HypervisorError:
172       pass
173
174     return (pidfile, pid, alive)
175
176   def _CheckDown(self, instance_name):
177     """Raises an error unless the given instance is down.
178
179     """
180     alive = self._InstancePidAlive(instance_name)[2]
181     if alive:
182       raise errors.HypervisorError("Failed to start instance %s: %s" %
183                                    (instance_name, "already running"))
184
185   @classmethod
186   def _InstanceMonitor(cls, instance_name):
187     """Returns the instance monitor socket name
188
189     """
190     return utils.PathJoin(cls._CTRL_DIR, "%s.monitor" % instance_name)
191
192   @classmethod
193   def _InstanceSerial(cls, instance_name):
194     """Returns the instance serial socket name
195
196     """
197     return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
198
199   @staticmethod
200   def _SocatUnixConsoleParams():
201     """Returns the correct parameters for socat
202
203     If we have a new-enough socat we can use raw mode with an escape character.
204
205     """
206     if constants.SOCAT_USE_ESCAPE:
207       return "raw,echo=0,escape=%s" % constants.SOCAT_ESCAPE_CODE
208     else:
209       return "echo=0,icanon=0"
210
211   @classmethod
212   def _InstanceKVMRuntime(cls, instance_name):
213     """Returns the instance KVM runtime filename
214
215     """
216     return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name)
217
218   @classmethod
219   def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
220     """Removes an instance's rutime sockets/files.
221
222     """
223     utils.RemoveFile(pidfile)
224     utils.RemoveFile(cls._InstanceMonitor(instance_name))
225     utils.RemoveFile(cls._InstanceSerial(instance_name))
226     utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
227
228   def _WriteNetScript(self, instance, seq, nic):
229     """Write a script to connect a net interface to the proper bridge.
230
231     This can be used by any qemu-type hypervisor.
232
233     @param instance: instance we're acting on
234     @type instance: instance object
235     @param seq: nic sequence number
236     @type seq: int
237     @param nic: nic we're acting on
238     @type nic: nic object
239     @return: netscript file name
240     @rtype: string
241
242     """
243     script = StringIO()
244     script.write("#!/bin/sh\n")
245     script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
246     script.write("PATH=$PATH:/sbin:/usr/sbin\n")
247     script.write("export INSTANCE=%s\n" % instance.name)
248     script.write("export MAC=%s\n" % nic.mac)
249     if nic.ip:
250       script.write("export IP=%s\n" % nic.ip)
251     script.write("export MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
252     if nic.nicparams[constants.NIC_LINK]:
253       script.write("export LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
254     if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
255       script.write("export BRIDGE=%s\n" % nic.nicparams[constants.NIC_LINK])
256     script.write("export INTERFACE=$1\n")
257     # TODO: make this configurable at ./configure time
258     script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
259     script.write("  # Execute the user-specific vif file\n")
260     script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
261     script.write("else\n")
262     script.write("  ifconfig $INTERFACE 0.0.0.0 up\n")
263     if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
264       script.write("  # Connect the interface to the bridge\n")
265       script.write("  brctl addif $BRIDGE $INTERFACE\n")
266     elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED:
267       if not nic.ip:
268         raise errors.HypervisorError("nic/%d is routed, but has no ip." % seq)
269       script.write("  # Route traffic targeted at the IP to the interface\n")
270       if nic.nicparams[constants.NIC_LINK]:
271         script.write("  while ip rule del dev $INTERFACE; do :; done\n")
272         script.write("  ip rule add dev $INTERFACE table $LINK\n")
273         script.write("  ip route replace $IP table $LINK proto static"
274                      " dev $INTERFACE\n")
275       else:
276         script.write("  ip route replace $IP proto static"
277                      " dev $INTERFACE\n")
278       interface_v4_conf = "/proc/sys/net/ipv4/conf/$INTERFACE"
279       interface_v6_conf = "/proc/sys/net/ipv6/conf/$INTERFACE"
280       script.write("  if [ -d %s ]; then\n" % interface_v4_conf)
281       script.write("    echo 1 > %s/proxy_arp\n" % interface_v4_conf)
282       script.write("    echo 1 > %s/forwarding\n" % interface_v4_conf)
283       script.write("  fi\n")
284       script.write("  if [ -d %s ]; then\n" % interface_v6_conf)
285       script.write("    echo 1 > %s/proxy_ndp\n" % interface_v6_conf)
286       script.write("    echo 1 > %s/forwarding\n" % interface_v6_conf)
287       script.write("  fi\n")
288     script.write("fi\n\n")
289     # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
290     # mounted noexec sometimes, so we'll have to find another place.
291     (tmpfd, tmpfile_name) = tempfile.mkstemp()
292     tmpfile = os.fdopen(tmpfd, 'w')
293     try:
294       tmpfile.write(script.getvalue())
295     finally:
296       tmpfile.close()
297     os.chmod(tmpfile_name, 0755)
298     return tmpfile_name
299
300   def ListInstances(self):
301     """Get the list of running instances.
302
303     We can do this by listing our live instances directory and
304     checking whether the associated kvm process is still alive.
305
306     """
307     result = []
308     for name in os.listdir(self._PIDS_DIR):
309       if self._InstancePidAlive(name)[2]:
310         result.append(name)
311     return result
312
313   def GetInstanceInfo(self, instance_name):
314     """Get instance properties.
315
316     @type instance_name: string
317     @param instance_name: the instance name
318     @rtype: tuple of strings
319     @return: (name, id, memory, vcpus, stat, times)
320
321     """
322     _, pid, alive = self._InstancePidAlive(instance_name)
323     if not alive:
324       return None
325
326     _, memory, vcpus = self._InstancePidInfo(pid)
327     stat = "---b-"
328     times = "0"
329
330     return (instance_name, pid, memory, vcpus, stat, times)
331
332   def GetAllInstancesInfo(self):
333     """Get properties of all instances.
334
335     @return: list of tuples (name, id, memory, vcpus, stat, times)
336
337     """
338     data = []
339     for name in os.listdir(self._PIDS_DIR):
340       try:
341         info = self.GetInstanceInfo(name)
342       except errors.HypervisorError:
343         continue
344       if info:
345         data.append(info)
346     return data
347
348   def _GenerateKVMRuntime(self, instance, block_devices):
349     """Generate KVM information to start an instance.
350
351     """
352     pidfile  = self._InstancePidFile(instance.name)
353     kvm = constants.KVM_PATH
354     kvm_cmd = [kvm]
355     # used just by the vnc server, if enabled
356     kvm_cmd.extend(['-name', instance.name])
357     kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
358     kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
359     kvm_cmd.extend(['-pidfile', pidfile])
360     kvm_cmd.extend(['-daemonize'])
361     if not instance.hvparams[constants.HV_ACPI]:
362       kvm_cmd.extend(['-no-acpi'])
363
364     hvp = instance.hvparams
365     boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
366     boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
367     boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
368
369     if boot_network:
370       kvm_cmd.extend(['-boot', 'n'])
371
372     disk_type = hvp[constants.HV_DISK_TYPE]
373     if disk_type == constants.HT_DISK_PARAVIRTUAL:
374       if_val = ',if=virtio'
375     else:
376       if_val = ',if=%s' % disk_type
377     # Cache mode
378     disk_cache = hvp[constants.HV_DISK_CACHE]
379     if disk_cache != constants.HT_CACHE_DEFAULT:
380       cache_val = ",cache=%s" % disk_cache
381     else:
382       cache_val = ""
383     for cfdev, dev_path in block_devices:
384       if cfdev.mode != constants.DISK_RDWR:
385         raise errors.HypervisorError("Instance has read-only disks which"
386                                      " are not supported by KVM")
387       # TODO: handle FD_LOOP and FD_BLKTAP (?)
388       if boot_disk:
389         kvm_cmd.extend(['-boot', 'c'])
390         boot_val = ',boot=on'
391         # We only boot from the first disk
392         boot_disk = False
393       else:
394         boot_val = ''
395
396       drive_val = 'file=%s,format=raw%s%s%s' % (dev_path, if_val, boot_val,
397                                                 cache_val)
398       kvm_cmd.extend(['-drive', drive_val])
399
400     iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
401     if iso_image:
402       options = ',format=raw,media=cdrom'
403       if boot_cdrom:
404         kvm_cmd.extend(['-boot', 'd'])
405         options = '%s,boot=on' % options
406       else:
407         options = '%s,if=virtio' % options
408       drive_val = 'file=%s%s' % (iso_image, options)
409       kvm_cmd.extend(['-drive', drive_val])
410
411     kernel_path = hvp[constants.HV_KERNEL_PATH]
412     if kernel_path:
413       kvm_cmd.extend(['-kernel', kernel_path])
414       initrd_path = hvp[constants.HV_INITRD_PATH]
415       if initrd_path:
416         kvm_cmd.extend(['-initrd', initrd_path])
417       root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
418                      hvp[constants.HV_KERNEL_ARGS]]
419       if hvp[constants.HV_SERIAL_CONSOLE]:
420         root_append.append('console=ttyS0,38400')
421       kvm_cmd.extend(['-append', ' '.join(root_append)])
422
423     mouse_type = hvp[constants.HV_USB_MOUSE]
424     if mouse_type:
425       kvm_cmd.extend(['-usb'])
426       kvm_cmd.extend(['-usbdevice', mouse_type])
427
428     vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
429     if vnc_bind_address:
430       if utils.IsValidIP(vnc_bind_address):
431         if instance.network_port > constants.VNC_BASE_PORT:
432           display = instance.network_port - constants.VNC_BASE_PORT
433           if vnc_bind_address == '0.0.0.0':
434             vnc_arg = ':%d' % (display)
435           else:
436             vnc_arg = '%s:%d' % (vnc_bind_address, display)
437         else:
438           logging.error("Network port is not a valid VNC display (%d < %d)."
439                         " Not starting VNC", instance.network_port,
440                         constants.VNC_BASE_PORT)
441           vnc_arg = 'none'
442
443         # Only allow tls and other option when not binding to a file, for now.
444         # kvm/qemu gets confused otherwise about the filename to use.
445         vnc_append = ''
446         if hvp[constants.HV_VNC_TLS]:
447           vnc_append = '%s,tls' % vnc_append
448           if hvp[constants.HV_VNC_X509_VERIFY]:
449             vnc_append = '%s,x509verify=%s' % (vnc_append,
450                                                hvp[constants.HV_VNC_X509])
451           elif hvp[constants.HV_VNC_X509]:
452             vnc_append = '%s,x509=%s' % (vnc_append,
453                                          hvp[constants.HV_VNC_X509])
454         if hvp[constants.HV_VNC_PASSWORD_FILE]:
455           vnc_append = '%s,password' % vnc_append
456
457         vnc_arg = '%s%s' % (vnc_arg, vnc_append)
458
459       else:
460         vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
461
462       kvm_cmd.extend(['-vnc', vnc_arg])
463     else:
464       kvm_cmd.extend(['-nographic'])
465
466     monitor_dev = ("unix:%s,server,nowait" %
467                    self._InstanceMonitor(instance.name))
468     kvm_cmd.extend(['-monitor', monitor_dev])
469     if hvp[constants.HV_SERIAL_CONSOLE]:
470       serial_dev = ('unix:%s,server,nowait' %
471                     self._InstanceSerial(instance.name))
472       kvm_cmd.extend(['-serial', serial_dev])
473     else:
474       kvm_cmd.extend(['-serial', 'none'])
475
476     if hvp[constants.HV_USE_LOCALTIME]:
477       kvm_cmd.extend(['-localtime'])
478
479     # Save the current instance nics, but defer their expansion as parameters,
480     # as we'll need to generate executable temp files for them.
481     kvm_nics = instance.nics
482     hvparams = hvp
483
484     return (kvm_cmd, kvm_nics, hvparams)
485
486   def _WriteKVMRuntime(self, instance_name, data):
487     """Write an instance's KVM runtime
488
489     """
490     try:
491       utils.WriteFile(self._InstanceKVMRuntime(instance_name),
492                       data=data)
493     except EnvironmentError, err:
494       raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
495
496   def _ReadKVMRuntime(self, instance_name):
497     """Read an instance's KVM runtime
498
499     """
500     try:
501       file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
502     except EnvironmentError, err:
503       raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
504     return file_content
505
506   def _SaveKVMRuntime(self, instance, kvm_runtime):
507     """Save an instance's KVM runtime
508
509     """
510     kvm_cmd, kvm_nics, hvparams = kvm_runtime
511     serialized_nics = [nic.ToDict() for nic in kvm_nics]
512     serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
513     self._WriteKVMRuntime(instance.name, serialized_form)
514
515   def _LoadKVMRuntime(self, instance, serialized_runtime=None):
516     """Load an instance's KVM runtime
517
518     """
519     if not serialized_runtime:
520       serialized_runtime = self._ReadKVMRuntime(instance.name)
521     loaded_runtime = serializer.Load(serialized_runtime)
522     kvm_cmd, serialized_nics, hvparams = loaded_runtime
523     kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
524     return (kvm_cmd, kvm_nics, hvparams)
525
526   def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
527     """Execute a KVM cmd, after completing it with some last minute data
528
529     @type incoming: tuple of strings
530     @param incoming: (target_host_ip, port)
531
532     """
533     hvp = instance.hvparams
534     name = instance.name
535     self._CheckDown(name)
536
537     temp_files = []
538
539     kvm_cmd, kvm_nics, hvparams = kvm_runtime
540
541     security_model = hvp[constants.HV_SECURITY_MODEL]
542     if security_model == constants.HT_SM_USER:
543       kvm_cmd.extend(["-runas", hvp[constants.HV_SECURITY_DOMAIN]])
544
545     if not kvm_nics:
546       kvm_cmd.extend(['-net', 'none'])
547     else:
548       nic_type = hvparams[constants.HV_NIC_TYPE]
549       if nic_type == constants.HT_NIC_PARAVIRTUAL:
550         nic_model = "model=virtio"
551       else:
552         nic_model = "model=%s" % nic_type
553
554       for nic_seq, nic in enumerate(kvm_nics):
555         nic_val = "nic,vlan=%s,macaddr=%s,%s" % (nic_seq, nic.mac, nic_model)
556         script = self._WriteNetScript(instance, nic_seq, nic)
557         kvm_cmd.extend(['-net', nic_val])
558         kvm_cmd.extend(['-net', 'tap,vlan=%s,script=%s' % (nic_seq, script)])
559         temp_files.append(script)
560
561     if incoming:
562       target, port = incoming
563       kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
564
565     vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
566     vnc_pwd = None
567     if vnc_pwd_file:
568       try:
569         vnc_pwd = utils.ReadFile(vnc_pwd_file)
570       except EnvironmentError, err:
571         raise errors.HypervisorError("Failed to open VNC password file %s: %s"
572                                      % (vnc_pwd_file, err))
573
574     result = utils.RunCmd(kvm_cmd)
575     if result.failed:
576       raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
577                                    (name, result.fail_reason, result.output))
578
579     if not self._InstancePidAlive(name)[2]:
580       raise errors.HypervisorError("Failed to start instance %s" % name)
581
582     if vnc_pwd:
583       change_cmd = 'change vnc password %s' % vnc_pwd
584       self._CallMonitorCommand(instance.name, change_cmd)
585
586     for filename in temp_files:
587       utils.RemoveFile(filename)
588
589   def StartInstance(self, instance, block_devices):
590     """Start an instance.
591
592     """
593     self._CheckDown(instance.name)
594     kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
595     self._SaveKVMRuntime(instance, kvm_runtime)
596     self._ExecuteKVMRuntime(instance, kvm_runtime)
597
598   def _CallMonitorCommand(self, instance_name, command):
599     """Invoke a command on the instance monitor.
600
601     """
602     socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
603              (utils.ShellQuote(command),
604               constants.SOCAT_PATH,
605               utils.ShellQuote(self._InstanceMonitor(instance_name))))
606     result = utils.RunCmd(socat)
607     if result.failed:
608       msg = ("Failed to send command '%s' to instance %s."
609              " output: %s, error: %s, fail_reason: %s" %
610              (command, instance_name,
611               result.stdout, result.stderr, result.fail_reason))
612       raise errors.HypervisorError(msg)
613
614     return result
615
616   def StopInstance(self, instance, force=False, retry=False, name=None):
617     """Stop an instance.
618
619     """
620     if name is not None and not force:
621       raise errors.HypervisorError("Cannot shutdown cleanly by name only")
622     if name is None:
623       name = instance.name
624       acpi = instance.hvparams[constants.HV_ACPI]
625     else:
626       acpi = False
627     pidfile, pid, alive = self._InstancePidAlive(name)
628     if pid > 0 and alive:
629       if force or not acpi:
630         utils.KillProcess(pid)
631       else:
632         self._CallMonitorCommand(name, 'system_powerdown')
633
634     if not self._InstancePidAlive(name)[2]:
635       self._RemoveInstanceRuntimeFiles(pidfile, name)
636       return True
637     else:
638       return False
639
640   def RebootInstance(self, instance):
641     """Reboot an instance.
642
643     """
644     # For some reason if we do a 'send-key ctrl-alt-delete' to the control
645     # socket the instance will stop, but now power up again. So we'll resort
646     # to shutdown and restart.
647     _, _, alive = self._InstancePidAlive(instance.name)
648     if not alive:
649       raise errors.HypervisorError("Failed to reboot instance %s:"
650                                    " not running" % instance.name)
651     # StopInstance will delete the saved KVM runtime so:
652     # ...first load it...
653     kvm_runtime = self._LoadKVMRuntime(instance)
654     # ...now we can safely call StopInstance...
655     if not self.StopInstance(instance):
656       self.StopInstance(instance, force=True)
657     # ...and finally we can save it again, and execute it...
658     self._SaveKVMRuntime(instance, kvm_runtime)
659     self._ExecuteKVMRuntime(instance, kvm_runtime)
660
661   def MigrationInfo(self, instance):
662     """Get instance information to perform a migration.
663
664     @type instance: L{objects.Instance}
665     @param instance: instance to be migrated
666     @rtype: string
667     @return: content of the KVM runtime file
668
669     """
670     return self._ReadKVMRuntime(instance.name)
671
672   def AcceptInstance(self, instance, info, target):
673     """Prepare to accept an instance.
674
675     @type instance: L{objects.Instance}
676     @param instance: instance to be accepted
677     @type info: string
678     @param info: content of the KVM runtime file on the source node
679     @type target: string
680     @param target: target host (usually ip), on this node
681
682     """
683     kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
684     incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT])
685     self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
686
687   def FinalizeMigration(self, instance, info, success):
688     """Finalize an instance migration.
689
690     Stop the incoming mode KVM.
691
692     @type instance: L{objects.Instance}
693     @param instance: instance whose migration is being aborted
694
695     """
696     if success:
697       self._WriteKVMRuntime(instance.name, info)
698     else:
699       self.StopInstance(instance, force=True)
700
701   def MigrateInstance(self, instance, target, live):
702     """Migrate an instance to a target node.
703
704     The migration will not be attempted if the instance is not
705     currently running.
706
707     @type instance: L{objects.Instance}
708     @param instance: the instance to be migrated
709     @type target: string
710     @param target: ip address of the target node
711     @type live: boolean
712     @param live: perform a live migration
713
714     """
715     instance_name = instance.name
716     port = instance.hvparams[constants.HV_MIGRATION_PORT]
717     pidfile, pid, alive = self._InstancePidAlive(instance_name)
718     if not alive:
719       raise errors.HypervisorError("Instance not running, cannot migrate")
720
721     if not utils.TcpPing(target, port, live_port_needed=True):
722       raise errors.HypervisorError("Remote host %s not listening on port"
723                                    " %s, cannot migrate" % (target, port))
724
725     if not live:
726       self._CallMonitorCommand(instance_name, 'stop')
727
728     migrate_command = 'migrate -d tcp:%s:%s' % (target, port)
729     self._CallMonitorCommand(instance_name, migrate_command)
730
731     info_command = 'info migrate'
732     done = False
733     broken_answers = 0
734     while not done:
735       result = self._CallMonitorCommand(instance_name, info_command)
736       match = self._MIGRATION_STATUS_RE.search(result.stdout)
737       if not match:
738         broken_answers += 1
739         if not result.stdout:
740           logging.info("KVM: empty 'info migrate' result")
741         else:
742           logging.warning("KVM: unknown 'info migrate' result: %s",
743                           result.stdout)
744         time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
745       else:
746         status = match.group(1)
747         if status == 'completed':
748           done = True
749         elif status == 'active':
750           # reset the broken answers count
751           broken_answers = 0
752           time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
753         elif status == 'failed' or status == 'cancelled':
754           if not live:
755             self._CallMonitorCommand(instance_name, 'cont')
756           raise errors.HypervisorError("Migration %s at the kvm level" %
757                                        status)
758         else:
759           logging.warning("KVM: unknown migration status '%s'", status)
760           broken_answers += 1
761           time.sleep(self._MIGRATION_INFO_RETRY_DELAY)
762       if broken_answers >= self._MIGRATION_INFO_MAX_BAD_ANSWERS:
763         raise errors.HypervisorError("Too many 'info migrate' broken answers")
764
765     utils.KillProcess(pid)
766     self._RemoveInstanceRuntimeFiles(pidfile, instance_name)
767
768   def GetNodeInfo(self):
769     """Return information about the node.
770
771     This is just a wrapper over the base GetLinuxNodeInfo method.
772
773     @return: a dict with the following keys (values in MiB):
774           - memory_total: the total memory size on the node
775           - memory_free: the available memory on the node for instances
776           - memory_dom0: the memory used by the node itself, if available
777
778     """
779     return self.GetLinuxNodeInfo()
780
781   @classmethod
782   def GetShellCommandForConsole(cls, instance, hvparams, beparams):
783     """Return a command for connecting to the console of an instance.
784
785     """
786     if hvparams[constants.HV_SERIAL_CONSOLE]:
787       shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
788                        (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
789                         utils.ShellQuote(cls._InstanceSerial(instance.name))))
790     else:
791       shell_command = "echo 'No serial shell for instance %s'" % instance.name
792
793     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
794     if vnc_bind_address:
795       if instance.network_port > constants.VNC_BASE_PORT:
796         display = instance.network_port - constants.VNC_BASE_PORT
797         vnc_command = ("echo 'Instance has VNC listening on %s:%d"
798                        " (display: %d)'" % (vnc_bind_address,
799                                             instance.network_port,
800                                             display))
801         shell_command = "%s; %s" % (vnc_command, shell_command)
802
803     return shell_command
804
805   def Verify(self):
806     """Verify the hypervisor.
807
808     Check that the binary exists.
809
810     """
811     if not os.path.exists(constants.KVM_PATH):
812       return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
813     if not os.path.exists(constants.SOCAT_PATH):
814       return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
815
816
817   @classmethod
818   def CheckParameterSyntax(cls, hvparams):
819     """Check the given parameters for validity.
820
821     @type hvparams:  dict
822     @param hvparams: dictionary with parameter names/value
823     @raise errors.HypervisorError: when a parameter is not valid
824
825     """
826     super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
827
828     kernel_path = hvparams[constants.HV_KERNEL_PATH]
829     if kernel_path:
830       if not hvparams[constants.HV_ROOT_PATH]:
831         raise errors.HypervisorError("Need a root partition for the instance,"
832                                      " if a kernel is defined")
833
834     if (hvparams[constants.HV_VNC_X509_VERIFY] and
835         not hvparams[constants.HV_VNC_X509]):
836       raise errors.HypervisorError("%s must be defined, if %s is" %
837                                    (constants.HV_VNC_X509,
838                                     constants.HV_VNC_X509_VERIFY))
839
840     boot_order = hvparams[constants.HV_BOOT_ORDER]
841     if (boot_order == constants.HT_BO_CDROM and
842         not hvparams[constants.HV_CDROM_IMAGE_PATH]):
843       raise errors.HypervisorError("Cannot boot from cdrom without an"
844                                    " ISO path")
845
846     security_model = hvparams[constants.HV_SECURITY_MODEL]
847     if security_model == constants.HT_SM_USER:
848       if not hvparams[constants.HV_SECURITY_DOMAIN]:
849         raise errors.HypervisorError("A security domain (user to run kvm as)"
850                                      " must be specified")
851     elif (security_model == constants.HT_SM_NONE or
852           security_model == constants.HT_SM_POOL):
853       if hvparams[constants.HV_SECURITY_DOMAIN]:
854         raise errors.HypervisorError("Cannot have a security domain when the"
855                                      " security model is 'none' or 'pool'")
856     if security_model == constants.HT_SM_POOL:
857       raise errors.HypervisorError("Security model pool is not supported yet")
858
859   @classmethod
860   def ValidateParameters(cls, hvparams):
861     """Check the given parameters for validity.
862
863     @type hvparams:  dict
864     @param hvparams: dictionary with parameter names/value
865     @raise errors.HypervisorError: when a parameter is not valid
866
867     """
868     super(KVMHypervisor, cls).ValidateParameters(hvparams)
869
870     security_model = hvparams[constants.HV_SECURITY_MODEL]
871     if security_model == constants.HT_SM_USER:
872       username = hvparams[constants.HV_SECURITY_DOMAIN]
873       try:
874         pwd.getpwnam(username)
875       except KeyError:
876         raise errors.HypervisorError("Unknown security domain user %s"
877                                      % username)
878
879   @classmethod
880   def PowercycleNode(cls):
881     """KVM powercycle, just a wrapper over Linux powercycle.
882
883     """
884     cls.LinuxPowercycle()