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(["xm", "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 = ["xm", "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 = ["xm", "destroy", name]
209 command = ["xm", "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(["xm", "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
262 # note: in xen 3, memory has changed to total_memory
263 result = utils.RunCmd(["xm", "info"])
265 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
269 xmoutput = result.stdout.splitlines()
271 cores_per_socket = threads_per_core = nr_cpus = None
272 for line in xmoutput:
273 splitfields = line.split(":", 1)
275 if len(splitfields) > 1:
276 key = splitfields[0].strip()
277 val = splitfields[1].strip()
278 if key == "memory" or key == "total_memory":
279 result["memory_total"] = int(val)
280 elif key == "free_memory":
281 result["memory_free"] = int(val)
282 elif key == "nr_cpus":
283 nr_cpus = result["cpu_total"] = int(val)
284 elif key == "nr_nodes":
285 result["cpu_nodes"] = int(val)
286 elif key == "cores_per_socket":
287 cores_per_socket = int(val)
288 elif key == "threads_per_core":
289 threads_per_core = int(val)
291 if (cores_per_socket is not None and
292 threads_per_core is not None and nr_cpus is not None):
293 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
295 dom0_info = self.GetInstanceInfo("Domain-0")
296 if dom0_info is not None:
297 result["memory_dom0"] = dom0_info[2]
302 def GetInstanceConsole(cls, instance, hvparams, beparams):
303 """Return a command for connecting to the console of an instance.
306 return objects.InstanceConsole(instance=instance.name,
307 kind=constants.CONS_SSH,
308 host=instance.primary_node,
309 user=constants.GANETI_RUNAS,
310 command=[constants.XM_CONSOLE_WRAPPER,
314 """Verify the hypervisor.
316 For Xen, this verifies that the xend process is running.
319 result = utils.RunCmd(["xm", "info"])
321 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
324 def _GetConfigFileDiskData(block_devices, blockdev_prefix):
325 """Get disk directive for xen config file.
327 This method builds the xen config disk directive according to the
328 given disk_template and block_devices.
330 @param block_devices: list of tuples (cfdev, rldev):
331 - cfdev: dict containing ganeti config disk part
332 - rldev: ganeti.bdev.BlockDev object
333 @param blockdev_prefix: a string containing blockdevice prefix,
334 e.g. "sd" for /dev/sda
336 @return: string containing disk directive for xen instance config file
340 constants.FD_LOOP: "file",
341 constants.FD_BLKTAP: "tap:aio",
344 if len(block_devices) > 24:
346 raise errors.HypervisorError("Too many disks")
347 namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
348 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
349 if cfdev.mode == constants.DISK_RDWR:
353 if cfdev.dev_type == constants.LD_FILE:
354 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
355 dev_path, sd_name, mode)
357 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
358 disk_data.append(line)
362 def MigrationInfo(self, instance):
363 """Get instance information to perform a migration.
365 @type instance: L{objects.Instance}
366 @param instance: instance to be migrated
368 @return: content of the xen config file
371 return self._ReadConfigFile(instance.name)
373 def AcceptInstance(self, instance, info, target):
374 """Prepare to accept an instance.
376 @type instance: L{objects.Instance}
377 @param instance: instance to be accepted
379 @param info: content of the xen config file on the source node
381 @param target: target host (usually ip), on this node
386 def FinalizeMigration(self, instance, info, success):
387 """Finalize an instance migration.
389 After a successful migration we write the xen config file.
390 We do nothing on a failure, as we did not change anything at accept time.
392 @type instance: L{objects.Instance}
393 @param instance: instance whose migration is being finalized
395 @param info: content of the xen config file on the source node
396 @type success: boolean
397 @param success: whether the migration was a success or a failure
401 self._WriteConfigFileStatic(instance.name, info)
403 def MigrateInstance(self, instance, target, live):
404 """Migrate an instance to a target node.
406 The migration will not be attempted if the instance is not
409 @type instance: L{objects.Instance}
410 @param instance: the instance to be migrated
412 @param target: ip address of the target node
414 @param live: perform a live migration
417 if self.GetInstanceInfo(instance.name) is None:
418 raise errors.HypervisorError("Instance not running, cannot migrate")
420 port = instance.hvparams[constants.HV_MIGRATION_PORT]
422 if not netutils.TcpPing(target, port, live_port_needed=True):
423 raise errors.HypervisorError("Remote host %s not listening on port"
424 " %s, cannot migrate" % (target, port))
426 args = ["xm", "migrate", "-p", "%d" % port]
429 args.extend([instance.name, target])
430 result = utils.RunCmd(args)
432 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
433 (instance.name, result.output))
434 # remove old xen file after migration succeeded
436 self._RemoveConfigFile(instance.name)
437 except EnvironmentError:
438 logging.exception("Failure while removing instance config file")
441 def PowercycleNode(cls):
442 """Xen-specific powercycle.
444 This first does a Linux reboot (which triggers automatically a Xen
445 reboot), and if that fails it tries to do a Xen reboot. The reason
446 we don't try a Xen reboot first is that the xen reboot launches an
447 external command which connects to the Xen hypervisor, and that
448 won't work in case the root filesystem is broken and/or the xend
449 daemon is not working.
453 cls.LinuxPowercycle()
455 utils.RunCmd(["xm", "debug", "R"])
458 class XenPvmHypervisor(XenHypervisor):
459 """Xen PVM hypervisor interface"""
462 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
463 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
464 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
465 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
466 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
467 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
468 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
469 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
470 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
471 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
472 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
473 constants.HV_REBOOT_BEHAVIOR:
474 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
478 def _WriteConfigFile(cls, instance, block_devices):
479 """Write the Xen config file for the instance.
482 hvp = instance.hvparams
484 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
486 # if bootloader is True, use bootloader instead of kernel and ramdisk
488 if hvp[constants.HV_USE_BOOTLOADER]:
489 # bootloader handling
490 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
492 config.write("bootloader = '%s'\n" % bootloader_path)
494 raise errors.HypervisorError("Bootloader enabled, but missing"
497 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
499 config.write("bootargs = '%s'\n" % bootloader_args)
502 kpath = hvp[constants.HV_KERNEL_PATH]
503 config.write("kernel = '%s'\n" % kpath)
506 initrd_path = hvp[constants.HV_INITRD_PATH]
508 config.write("ramdisk = '%s'\n" % initrd_path)
510 # rest of the settings
511 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
512 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
513 config.write("name = '%s'\n" % instance.name)
516 for nic in instance.nics:
517 nic_str = "mac=%s" % (nic.mac)
518 ip = getattr(nic, "ip", None)
520 nic_str += ", ip=%s" % ip
521 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
522 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
523 vif_data.append("'%s'" % nic_str)
525 disk_data = cls._GetConfigFileDiskData(block_devices,
526 hvp[constants.HV_BLOCKDEV_PREFIX])
528 config.write("vif = [%s]\n" % ",".join(vif_data))
529 config.write("disk = [%s]\n" % ",".join(disk_data))
531 if hvp[constants.HV_ROOT_PATH]:
532 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
533 config.write("on_poweroff = 'destroy'\n")
534 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
535 config.write("on_reboot = 'restart'\n")
537 config.write("on_reboot = 'destroy'\n")
538 config.write("on_crash = 'restart'\n")
539 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
540 # just in case it exists
541 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
543 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
544 except EnvironmentError, err:
545 raise errors.HypervisorError("Cannot write Xen instance confile"
546 " file /etc/xen/%s: %s" %
547 (instance.name, err))
552 class XenHvmHypervisor(XenHypervisor):
553 """Xen HVM hypervisor interface"""
555 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
556 constants.VNC_PASSWORD_FILE,
560 constants.HV_ACPI: hv_base.NO_CHECK,
561 constants.HV_BOOT_ORDER: (True, ) +
562 (lambda x: x and len(x.strip("acdn")) == 0,
563 "Invalid boot order specified, must be one or more of [acdn]",
565 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
566 constants.HV_DISK_TYPE:
567 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
568 constants.HV_NIC_TYPE:
569 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
570 constants.HV_PAE: hv_base.NO_CHECK,
571 constants.HV_VNC_BIND_ADDRESS:
572 (False, netutils.IP4Address.IsValid,
573 "VNC bind address is not a valid IP address", None, None),
574 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
575 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
576 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
577 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
578 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
579 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
580 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
581 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
582 constants.HV_REBOOT_BEHAVIOR:
583 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
587 def _WriteConfigFile(cls, instance, block_devices):
588 """Create a Xen 3.1 HVM config file.
591 hvp = instance.hvparams
594 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
597 kpath = hvp[constants.HV_KERNEL_PATH]
598 config.write("kernel = '%s'\n" % kpath)
600 config.write("builder = 'hvm'\n")
601 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
602 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
603 config.write("name = '%s'\n" % instance.name)
604 if hvp[constants.HV_PAE]:
605 config.write("pae = 1\n")
607 config.write("pae = 0\n")
608 if hvp[constants.HV_ACPI]:
609 config.write("acpi = 1\n")
611 config.write("acpi = 0\n")
612 config.write("apic = 1\n")
613 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
614 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
615 config.write("sdl = 0\n")
616 config.write("usb = 1\n")
617 config.write("usbdevice = 'tablet'\n")
618 config.write("vnc = 1\n")
619 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
620 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
622 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
624 if instance.network_port > constants.VNC_BASE_PORT:
625 display = instance.network_port - constants.VNC_BASE_PORT
626 config.write("vncdisplay = %s\n" % display)
627 config.write("vncunused = 0\n")
629 config.write("# vncdisplay = 1\n")
630 config.write("vncunused = 1\n")
632 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
634 password = utils.ReadFile(vnc_pwd_file)
635 except EnvironmentError, err:
636 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
639 config.write("vncpasswd = '%s'\n" % password.rstrip())
641 config.write("serial = 'pty'\n")
642 if hvp[constants.HV_USE_LOCALTIME]:
643 config.write("localtime = 1\n")
646 nic_type = hvp[constants.HV_NIC_TYPE]
648 # ensure old instances don't change
649 nic_type_str = ", type=ioemu"
650 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
651 nic_type_str = ", type=paravirtualized"
653 nic_type_str = ", model=%s, type=ioemu" % nic_type
654 for nic in instance.nics:
655 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
656 ip = getattr(nic, "ip", None)
658 nic_str += ", ip=%s" % ip
659 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
660 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
661 vif_data.append("'%s'" % nic_str)
663 config.write("vif = [%s]\n" % ",".join(vif_data))
665 disk_data = cls._GetConfigFileDiskData(block_devices,
666 hvp[constants.HV_BLOCKDEV_PREFIX])
668 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
670 iso = "'file:%s,hdc:cdrom,r'" % iso_path
671 disk_data.append(iso)
673 config.write("disk = [%s]\n" % (",".join(disk_data)))
675 config.write("on_poweroff = 'destroy'\n")
676 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
677 config.write("on_reboot = 'restart'\n")
679 config.write("on_reboot = 'destroy'\n")
680 config.write("on_crash = 'restart'\n")
681 # just in case it exists
682 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
684 utils.WriteFile("/etc/xen/%s" % instance.name,
685 data=config.getvalue())
686 except EnvironmentError, err:
687 raise errors.HypervisorError("Cannot write Xen instance confile"
688 " file /etc/xen/%s: %s" %
689 (instance.name, err))