4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
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.
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.
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
27 from cStringIO import StringIO
29 from ganeti import constants
30 from ganeti import errors
31 from ganeti import utils
32 from ganeti.hypervisor import hv_base
33 from ganeti import netutils
34 from ganeti import objects
37 class XenHypervisor(hv_base.BaseHypervisor):
38 """Xen generic hypervisor interface
40 This is the Xen base class used for both Xen PVM and HVM. It contains
41 all the functionality that is identical for both.
45 REBOOT_RETRY_COUNT = 60
46 REBOOT_RETRY_INTERVAL = 10
49 "/etc/xen/xend-config.sxp",
50 "/etc/xen/scripts/vif-bridge",
54 def _WriteConfigFile(cls, instance, block_devices):
55 """Write the Xen config file for the instance.
58 raise NotImplementedError
61 def _WriteConfigFileStatic(instance_name, data):
62 """Write the Xen config file for the instance.
64 This version of the function just writes the config file from static data.
67 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
70 def _ReadConfigFile(instance_name):
71 """Returns the contents of the instance config file.
75 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
76 except EnvironmentError, err:
77 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
81 def _RemoveConfigFile(instance_name):
82 """Remove the xen configuration file.
85 utils.RemoveFile("/etc/xen/%s" % instance_name)
88 def _RunXmList(xmlist_errors):
89 """Helper function for L{_GetXMList} to run "xm list".
92 result = utils.RunCmd([constants.XEN_CMD, "list"])
94 logging.error("xm list failed (%s): %s", result.fail_reason,
96 xmlist_errors.append(result)
97 raise utils.RetryAgain()
99 # skip over the heading
100 return result.stdout.splitlines()[1:]
103 def _GetXMList(cls, include_node):
104 """Return the list of running instances.
106 If the include_node argument is True, then we return information
107 for dom0 also, otherwise we filter that from the return value.
109 @return: list of (name, id, memory, vcpus, state, time spent)
114 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
115 except utils.RetryTimeout:
117 xmlist_result = xmlist_errors.pop()
119 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
120 (xmlist_result.fail_reason, xmlist_result.output))
122 errmsg = "xm list failed"
124 raise errors.HypervisorError(errmsg)
128 # The format of lines is:
129 # Name ID Mem(MiB) VCPUs State Time(s)
130 # Domain-0 0 3418 4 r----- 266.2
133 raise errors.HypervisorError("Can't parse output of xm list,"
136 data[1] = int(data[1])
137 data[2] = int(data[2])
138 data[3] = int(data[3])
139 data[5] = float(data[5])
140 except (TypeError, ValueError), err:
141 raise errors.HypervisorError("Can't parse output of xm list,"
142 " line: %s, error: %s" % (line, err))
144 # skip the Domain-0 (optional)
145 if include_node or data[0] != "Domain-0":
150 def ListInstances(self):
151 """Get the list of running instances.
154 xm_list = self._GetXMList(False)
155 names = [info[0] for info in xm_list]
158 def GetInstanceInfo(self, instance_name):
159 """Get instance properties.
161 @param instance_name: the instance name
163 @return: tuple (name, id, memory, vcpus, stat, times)
166 xm_list = self._GetXMList(instance_name == "Domain-0")
169 if data[0] == instance_name:
174 def GetAllInstancesInfo(self):
175 """Get properties of all instances.
177 @return: list of tuples (name, id, memory, vcpus, stat, times)
180 xm_list = self._GetXMList(False)
183 def StartInstance(self, instance, block_devices, startup_paused):
184 """Start an instance.
187 self._WriteConfigFile(instance, block_devices)
188 cmd = [constants.XEN_CMD, "create"]
190 cmd.extend(["--paused"])
191 cmd.extend([instance.name])
192 result = utils.RunCmd(cmd)
195 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
196 (instance.name, result.fail_reason,
199 def StopInstance(self, instance, force=False, retry=False, name=None):
205 self._RemoveConfigFile(name)
207 command = [constants.XEN_CMD, "destroy", name]
209 command = [constants.XEN_CMD, "shutdown", name]
210 result = utils.RunCmd(command)
213 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
214 (name, result.fail_reason, result.output))
216 def RebootInstance(self, instance):
217 """Reboot an instance.
220 ini_info = self.GetInstanceInfo(instance.name)
223 raise errors.HypervisorError("Failed to reboot instance %s,"
224 " not running" % instance.name)
226 result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
228 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
229 (instance.name, result.fail_reason,
232 def _CheckInstance():
233 new_info = self.GetInstanceInfo(instance.name)
235 # check if the domain ID has changed or the run time has decreased
236 if (new_info is not None and
237 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
240 raise utils.RetryAgain()
243 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
244 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
245 except utils.RetryTimeout:
246 raise errors.HypervisorError("Failed to reboot instance %s: instance"
247 " did not reboot in the expected interval" %
250 def GetNodeInfo(self):
251 """Return information about the node.
253 @return: a dict with the following keys (memory values in MiB):
254 - memory_total: the total memory size on the node
255 - memory_free: the available memory on the node for instances
256 - memory_dom0: the memory used by the node itself, if available
257 - nr_cpus: total number of CPUs
258 - nr_nodes: in a NUMA system, the number of domains
259 - nr_sockets: the number of physical CPU sockets in the node
260 - hv_version: the hypervisor version in the form (major, minor)
263 # note: in xen 3, memory has changed to total_memory
264 result = utils.RunCmd([constants.XEN_CMD, "info"])
266 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
270 xmoutput = result.stdout.splitlines()
272 cores_per_socket = threads_per_core = nr_cpus = None
273 xen_major, xen_minor = None, None
274 for line in xmoutput:
275 splitfields = line.split(":", 1)
277 if len(splitfields) > 1:
278 key = splitfields[0].strip()
279 val = splitfields[1].strip()
280 if key == "memory" or key == "total_memory":
281 result["memory_total"] = int(val)
282 elif key == "free_memory":
283 result["memory_free"] = int(val)
284 elif key == "nr_cpus":
285 nr_cpus = result["cpu_total"] = int(val)
286 elif key == "nr_nodes":
287 result["cpu_nodes"] = int(val)
288 elif key == "cores_per_socket":
289 cores_per_socket = int(val)
290 elif key == "threads_per_core":
291 threads_per_core = int(val)
292 elif key == "xen_major":
294 elif key == "xen_minor":
297 if (cores_per_socket is not None and
298 threads_per_core is not None and nr_cpus is not None):
299 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
301 dom0_info = self.GetInstanceInfo("Domain-0")
302 if dom0_info is not None:
303 result["memory_dom0"] = dom0_info[2]
305 if not (xen_major is None or xen_minor is None):
306 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
311 def GetInstanceConsole(cls, instance, hvparams, beparams):
312 """Return a command for connecting to the console of an instance.
315 return objects.InstanceConsole(instance=instance.name,
316 kind=constants.CONS_SSH,
317 host=instance.primary_node,
318 user=constants.GANETI_RUNAS,
319 command=[constants.XM_CONSOLE_WRAPPER,
323 """Verify the hypervisor.
325 For Xen, this verifies that the xend process is running.
328 result = utils.RunCmd([constants.XEN_CMD, "info"])
330 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
333 def _GetConfigFileDiskData(block_devices, blockdev_prefix):
334 """Get disk directive for xen config file.
336 This method builds the xen config disk directive according to the
337 given disk_template and block_devices.
339 @param block_devices: list of tuples (cfdev, rldev):
340 - cfdev: dict containing ganeti config disk part
341 - rldev: ganeti.bdev.BlockDev object
342 @param blockdev_prefix: a string containing blockdevice prefix,
343 e.g. "sd" for /dev/sda
345 @return: string containing disk directive for xen instance config file
349 constants.FD_LOOP: "file",
350 constants.FD_BLKTAP: "tap:aio",
353 if len(block_devices) > 24:
355 raise errors.HypervisorError("Too many disks")
356 namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
357 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
358 if cfdev.mode == constants.DISK_RDWR:
362 if cfdev.dev_type == constants.LD_FILE:
363 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
364 dev_path, sd_name, mode)
366 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
367 disk_data.append(line)
371 def MigrationInfo(self, instance):
372 """Get instance information to perform a migration.
374 @type instance: L{objects.Instance}
375 @param instance: instance to be migrated
377 @return: content of the xen config file
380 return self._ReadConfigFile(instance.name)
382 def AcceptInstance(self, instance, info, target):
383 """Prepare to accept an instance.
385 @type instance: L{objects.Instance}
386 @param instance: instance to be accepted
388 @param info: content of the xen config file on the source node
390 @param target: target host (usually ip), on this node
395 def FinalizeMigration(self, instance, info, success):
396 """Finalize an instance migration.
398 After a successful migration we write the xen config file.
399 We do nothing on a failure, as we did not change anything at accept time.
401 @type instance: L{objects.Instance}
402 @param instance: instance whose migration is being finalized
404 @param info: content of the xen config file on the source node
405 @type success: boolean
406 @param success: whether the migration was a success or a failure
410 self._WriteConfigFileStatic(instance.name, info)
412 def MigrateInstance(self, instance, target, live):
413 """Migrate an instance to a target node.
415 The migration will not be attempted if the instance is not
418 @type instance: L{objects.Instance}
419 @param instance: the instance to be migrated
421 @param target: ip address of the target node
423 @param live: perform a live migration
426 if self.GetInstanceInfo(instance.name) is None:
427 raise errors.HypervisorError("Instance not running, cannot migrate")
429 port = instance.hvparams[constants.HV_MIGRATION_PORT]
431 if not netutils.TcpPing(target, port, live_port_needed=True):
432 raise errors.HypervisorError("Remote host %s not listening on port"
433 " %s, cannot migrate" % (target, port))
435 args = [constants.XEN_CMD, "migrate", "-p", "%d" % port]
438 args.extend([instance.name, target])
439 result = utils.RunCmd(args)
441 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
442 (instance.name, result.output))
443 # remove old xen file after migration succeeded
445 self._RemoveConfigFile(instance.name)
446 except EnvironmentError:
447 logging.exception("Failure while removing instance config file")
450 def PowercycleNode(cls):
451 """Xen-specific powercycle.
453 This first does a Linux reboot (which triggers automatically a Xen
454 reboot), and if that fails it tries to do a Xen reboot. The reason
455 we don't try a Xen reboot first is that the xen reboot launches an
456 external command which connects to the Xen hypervisor, and that
457 won't work in case the root filesystem is broken and/or the xend
458 daemon is not working.
462 cls.LinuxPowercycle()
464 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
467 class XenPvmHypervisor(XenHypervisor):
468 """Xen PVM hypervisor interface"""
471 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
472 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
473 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
474 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
475 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
476 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
477 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
478 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
479 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
480 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
481 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
482 constants.HV_REBOOT_BEHAVIOR:
483 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
487 def _WriteConfigFile(cls, instance, block_devices):
488 """Write the Xen config file for the instance.
491 hvp = instance.hvparams
493 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
495 # if bootloader is True, use bootloader instead of kernel and ramdisk
497 if hvp[constants.HV_USE_BOOTLOADER]:
498 # bootloader handling
499 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
501 config.write("bootloader = '%s'\n" % bootloader_path)
503 raise errors.HypervisorError("Bootloader enabled, but missing"
506 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
508 config.write("bootargs = '%s'\n" % bootloader_args)
511 kpath = hvp[constants.HV_KERNEL_PATH]
512 config.write("kernel = '%s'\n" % kpath)
515 initrd_path = hvp[constants.HV_INITRD_PATH]
517 config.write("ramdisk = '%s'\n" % initrd_path)
519 # rest of the settings
520 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
521 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
522 config.write("name = '%s'\n" % instance.name)
525 for nic in instance.nics:
526 nic_str = "mac=%s" % (nic.mac)
527 ip = getattr(nic, "ip", None)
529 nic_str += ", ip=%s" % ip
530 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
531 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
532 vif_data.append("'%s'" % nic_str)
534 disk_data = cls._GetConfigFileDiskData(block_devices,
535 hvp[constants.HV_BLOCKDEV_PREFIX])
537 config.write("vif = [%s]\n" % ",".join(vif_data))
538 config.write("disk = [%s]\n" % ",".join(disk_data))
540 if hvp[constants.HV_ROOT_PATH]:
541 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
542 config.write("on_poweroff = 'destroy'\n")
543 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
544 config.write("on_reboot = 'restart'\n")
546 config.write("on_reboot = 'destroy'\n")
547 config.write("on_crash = 'restart'\n")
548 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
549 # just in case it exists
550 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
552 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
553 except EnvironmentError, err:
554 raise errors.HypervisorError("Cannot write Xen instance confile"
555 " file /etc/xen/%s: %s" %
556 (instance.name, err))
561 class XenHvmHypervisor(XenHypervisor):
562 """Xen HVM hypervisor interface"""
564 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
565 constants.VNC_PASSWORD_FILE,
569 constants.HV_ACPI: hv_base.NO_CHECK,
570 constants.HV_BOOT_ORDER: (True, ) +
571 (lambda x: x and len(x.strip("acdn")) == 0,
572 "Invalid boot order specified, must be one or more of [acdn]",
574 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
575 constants.HV_DISK_TYPE:
576 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
577 constants.HV_NIC_TYPE:
578 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
579 constants.HV_PAE: hv_base.NO_CHECK,
580 constants.HV_VNC_BIND_ADDRESS:
581 (False, netutils.IP4Address.IsValid,
582 "VNC bind address is not a valid IP address", None, None),
583 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
584 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
585 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
586 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
587 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
588 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
589 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
590 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
591 constants.HV_REBOOT_BEHAVIOR:
592 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
596 def _WriteConfigFile(cls, instance, block_devices):
597 """Create a Xen 3.1 HVM config file.
600 hvp = instance.hvparams
603 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
606 kpath = hvp[constants.HV_KERNEL_PATH]
607 config.write("kernel = '%s'\n" % kpath)
609 config.write("builder = 'hvm'\n")
610 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
611 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
612 config.write("name = '%s'\n" % instance.name)
613 if hvp[constants.HV_PAE]:
614 config.write("pae = 1\n")
616 config.write("pae = 0\n")
617 if hvp[constants.HV_ACPI]:
618 config.write("acpi = 1\n")
620 config.write("acpi = 0\n")
621 config.write("apic = 1\n")
622 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
623 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
624 config.write("sdl = 0\n")
625 config.write("usb = 1\n")
626 config.write("usbdevice = 'tablet'\n")
627 config.write("vnc = 1\n")
628 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
629 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
631 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
633 if instance.network_port > constants.VNC_BASE_PORT:
634 display = instance.network_port - constants.VNC_BASE_PORT
635 config.write("vncdisplay = %s\n" % display)
636 config.write("vncunused = 0\n")
638 config.write("# vncdisplay = 1\n")
639 config.write("vncunused = 1\n")
641 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
643 password = utils.ReadFile(vnc_pwd_file)
644 except EnvironmentError, err:
645 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
648 config.write("vncpasswd = '%s'\n" % password.rstrip())
650 config.write("serial = 'pty'\n")
651 if hvp[constants.HV_USE_LOCALTIME]:
652 config.write("localtime = 1\n")
655 nic_type = hvp[constants.HV_NIC_TYPE]
657 # ensure old instances don't change
658 nic_type_str = ", type=ioemu"
659 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
660 nic_type_str = ", type=paravirtualized"
662 nic_type_str = ", model=%s, type=ioemu" % nic_type
663 for nic in instance.nics:
664 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
665 ip = getattr(nic, "ip", None)
667 nic_str += ", ip=%s" % ip
668 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
669 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
670 vif_data.append("'%s'" % nic_str)
672 config.write("vif = [%s]\n" % ",".join(vif_data))
674 disk_data = cls._GetConfigFileDiskData(block_devices,
675 hvp[constants.HV_BLOCKDEV_PREFIX])
677 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
679 iso = "'file:%s,hdc:cdrom,r'" % iso_path
680 disk_data.append(iso)
682 config.write("disk = [%s]\n" % (",".join(disk_data)))
684 config.write("on_poweroff = 'destroy'\n")
685 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
686 config.write("on_reboot = 'restart'\n")
688 config.write("on_reboot = 'destroy'\n")
689 config.write("on_crash = 'restart'\n")
690 # just in case it exists
691 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
693 utils.WriteFile("/etc/xen/%s" % instance.name,
694 data=config.getvalue())
695 except EnvironmentError, err:
696 raise errors.HypervisorError("Cannot write Xen instance confile"
697 " file /etc/xen/%s: %s" %
698 (instance.name, err))