4 # Copyright (C) 2006, 2007, 2008 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
35 class XenHypervisor(hv_base.BaseHypervisor):
36 """Xen generic hypervisor interface
38 This is the Xen base class used for both Xen PVM and HVM. It contains
39 all the functionality that is identical for both.
42 REBOOT_RETRY_COUNT = 60
43 REBOOT_RETRY_INTERVAL = 10
46 '/etc/xen/xend-config.sxp',
47 '/etc/xen/scripts/vif-bridge',
51 def _WriteConfigFile(cls, instance, block_devices):
52 """Write the Xen config file for the instance.
55 raise NotImplementedError
58 def _WriteConfigFileStatic(instance_name, data):
59 """Write the Xen config file for the instance.
61 This version of the function just writes the config file from static data.
64 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
67 def _ReadConfigFile(instance_name):
68 """Returns the contents of the instance config file.
72 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
73 except EnvironmentError, err:
74 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
78 def _RemoveConfigFile(instance_name):
79 """Remove the xen configuration file.
82 utils.RemoveFile("/etc/xen/%s" % instance_name)
85 def _RunXmList(xmlist_errors):
86 """Helper function for L{_GetXMList} to run "xm list".
89 result = utils.RunCmd(["xm", "list"])
91 logging.error("xm list failed (%s): %s", result.fail_reason,
93 xmlist_errors.append(result)
94 raise utils.RetryAgain()
96 # skip over the heading
97 return result.stdout.splitlines()[1:]
100 def _GetXMList(cls, include_node):
101 """Return the list of running instances.
103 If the include_node argument is True, then we return information
104 for dom0 also, otherwise we filter that from the return value.
106 @return: list of (name, id, memory, vcpus, state, time spent)
111 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
112 except utils.RetryTimeout:
114 xmlist_result = xmlist_errors.pop()
116 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
117 (xmlist_result.fail_reason, xmlist_result.output))
119 errmsg = "xm list failed"
121 raise errors.HypervisorError(errmsg)
125 # The format of lines is:
126 # Name ID Mem(MiB) VCPUs State Time(s)
127 # Domain-0 0 3418 4 r----- 266.2
130 raise errors.HypervisorError("Can't parse output of xm list,"
133 data[1] = int(data[1])
134 data[2] = int(data[2])
135 data[3] = int(data[3])
136 data[5] = float(data[5])
137 except (TypeError, ValueError), err:
138 raise errors.HypervisorError("Can't parse output of xm list,"
139 " line: %s, error: %s" % (line, err))
141 # skip the Domain-0 (optional)
142 if include_node or data[0] != 'Domain-0':
147 def ListInstances(self):
148 """Get the list of running instances.
151 xm_list = self._GetXMList(False)
152 names = [info[0] for info in xm_list]
155 def GetInstanceInfo(self, instance_name):
156 """Get instance properties.
158 @param instance_name: the instance name
160 @return: tuple (name, id, memory, vcpus, stat, times)
163 xm_list = self._GetXMList(instance_name=="Domain-0")
166 if data[0] == instance_name:
171 def GetAllInstancesInfo(self):
172 """Get properties of all instances.
174 @return: list of tuples (name, id, memory, vcpus, stat, times)
177 xm_list = self._GetXMList(False)
180 def StartInstance(self, instance, block_devices):
181 """Start an instance.
184 self._WriteConfigFile(instance, block_devices)
185 result = utils.RunCmd(["xm", "create", instance.name])
188 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
189 (instance.name, result.fail_reason,
192 def StopInstance(self, instance, force=False, retry=False):
196 self._RemoveConfigFile(instance.name)
198 command = ["xm", "destroy", instance.name]
200 command = ["xm", "shutdown", instance.name]
201 result = utils.RunCmd(command)
204 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
205 (instance.name, result.fail_reason,
208 def RebootInstance(self, instance):
209 """Reboot an instance.
212 ini_info = self.GetInstanceInfo(instance.name)
214 result = utils.RunCmd(["xm", "reboot", instance.name])
216 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
217 (instance.name, result.fail_reason,
220 def _CheckInstance():
221 new_info = self.GetInstanceInfo(instance.name)
223 # check if the domain ID has changed or the run time has decreased
224 if new_info[1] != ini_info[1] or new_info[5] < ini_info[5]:
227 raise utils.RetryAgain()
230 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
231 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
232 except utils.RetryTimeout:
233 raise errors.HypervisorError("Failed to reboot instance %s: instance"
234 " did not reboot in the expected interval" %
237 def GetNodeInfo(self):
238 """Return information about the node.
240 @return: a dict with the following keys (memory values in MiB):
241 - memory_total: the total memory size on the node
242 - memory_free: the available memory on the node for instances
243 - memory_dom0: the memory used by the node itself, if available
244 - nr_cpus: total number of CPUs
245 - nr_nodes: in a NUMA system, the number of domains
246 - nr_sockets: the number of physical CPU sockets in the node
249 # note: in xen 3, memory has changed to total_memory
250 result = utils.RunCmd(["xm", "info"])
252 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
256 xmoutput = result.stdout.splitlines()
258 cores_per_socket = threads_per_core = nr_cpus = None
259 for line in xmoutput:
260 splitfields = line.split(":", 1)
262 if len(splitfields) > 1:
263 key = splitfields[0].strip()
264 val = splitfields[1].strip()
265 if key == 'memory' or key == 'total_memory':
266 result['memory_total'] = int(val)
267 elif key == 'free_memory':
268 result['memory_free'] = int(val)
269 elif key == 'nr_cpus':
270 nr_cpus = result['cpu_total'] = int(val)
271 elif key == 'nr_nodes':
272 result['cpu_nodes'] = int(val)
273 elif key == 'cores_per_socket':
274 cores_per_socket = int(val)
275 elif key == 'threads_per_core':
276 threads_per_core = int(val)
278 if (cores_per_socket is not None and
279 threads_per_core is not None and nr_cpus is not None):
280 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
282 dom0_info = self.GetInstanceInfo("Domain-0")
283 if dom0_info is not None:
284 result['memory_dom0'] = dom0_info[2]
289 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
290 """Return a command for connecting to the console of an instance.
293 return "xm console %s" % instance.name
297 """Verify the hypervisor.
299 For Xen, this verifies that the xend process is running.
302 result = utils.RunCmd(["xm", "info"])
304 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
307 def _GetConfigFileDiskData(block_devices):
308 """Get disk directive for xen config file.
310 This method builds the xen config disk directive according to the
311 given disk_template and block_devices.
313 @param block_devices: list of tuples (cfdev, rldev):
314 - cfdev: dict containing ganeti config disk part
315 - rldev: ganeti.bdev.BlockDev object
317 @return: string containing disk directive for xen instance config file
321 constants.FD_LOOP: "file",
322 constants.FD_BLKTAP: "tap:aio",
325 if len(block_devices) > 24:
327 raise errors.HypervisorError("Too many disks")
328 # FIXME: instead of this hardcoding here, each of PVM/HVM should
329 # directly export their info (currently HVM will just sed this info)
330 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
331 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
332 if cfdev.mode == constants.DISK_RDWR:
336 if cfdev.dev_type == constants.LD_FILE:
337 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
338 dev_path, sd_name, mode)
340 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
341 disk_data.append(line)
345 def MigrationInfo(self, instance):
346 """Get instance information to perform a migration.
348 @type instance: L{objects.Instance}
349 @param instance: instance to be migrated
351 @return: content of the xen config file
354 return self._ReadConfigFile(instance.name)
356 def AcceptInstance(self, instance, info, target):
357 """Prepare to accept an instance.
359 @type instance: L{objects.Instance}
360 @param instance: instance to be accepted
362 @param info: content of the xen config file on the source node
364 @param target: target host (usually ip), on this node
369 def FinalizeMigration(self, instance, info, success):
370 """Finalize an instance migration.
372 After a successful migration we write the xen config file.
373 We do nothing on a failure, as we did not change anything at accept time.
375 @type instance: L{objects.Instance}
376 @param instance: instance whose migration is being aborted
378 @param info: content of the xen config file on the source node
379 @type success: boolean
380 @param success: whether the migration was a success or a failure
384 self._WriteConfigFileStatic(instance.name, info)
386 def MigrateInstance(self, instance, target, live):
387 """Migrate an instance to a target node.
389 The migration will not be attempted if the instance is not
392 @type instance: L{objects.Instance}
393 @param instance: the instance to be migrated
395 @param target: ip address of the target node
397 @param live: perform a live migration
400 if self.GetInstanceInfo(instance.name) is None:
401 raise errors.HypervisorError("Instance not running, cannot migrate")
403 port = instance.hvparams[constants.HV_MIGRATION_PORT]
405 if not utils.TcpPing(target, port, live_port_needed=True):
406 raise errors.HypervisorError("Remote host %s not listening on port"
407 " %s, cannot migrate" % (target, port))
409 args = ["xm", "migrate", "-p", "%d" % port]
412 args.extend([instance.name, target])
413 result = utils.RunCmd(args)
415 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
416 (instance.name, result.output))
417 # remove old xen file after migration succeeded
419 self._RemoveConfigFile(instance.name)
420 except EnvironmentError:
421 logging.exception("Failure while removing instance config file")
424 def PowercycleNode(cls):
425 """Xen-specific powercycle.
427 This first does a Linux reboot (which triggers automatically a Xen
428 reboot), and if that fails it tries to do a Xen reboot. The reason
429 we don't try a Xen reboot first is that the xen reboot launches an
430 external command which connects to the Xen hypervisor, and that
431 won't work in case the root filesystem is broken and/or the xend
432 daemon is not working.
436 cls.LinuxPowercycle()
438 utils.RunCmd(["xm", "debug", "R"])
441 class XenPvmHypervisor(XenHypervisor):
442 """Xen PVM hypervisor interface"""
445 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
446 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
447 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
448 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
449 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
450 constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK,
451 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
452 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
456 def _WriteConfigFile(cls, instance, block_devices):
457 """Write the Xen config file for the instance.
460 hvp = instance.hvparams
462 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
464 # if bootloader is True, use bootloader instead of kernel and ramdisk
466 if hvp[constants.HV_USE_BOOTLOADER]:
467 # bootloader handling
468 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
470 config.write("bootloader = '%s'\n" % bootloader_path)
472 raise errors.HypervisorError("Bootloader enabled, but missing"
475 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
477 config.write("bootargs = '%s'\n" % bootloader_args)
480 kpath = hvp[constants.HV_KERNEL_PATH]
481 config.write("kernel = '%s'\n" % kpath)
484 initrd_path = hvp[constants.HV_INITRD_PATH]
486 config.write("ramdisk = '%s'\n" % initrd_path)
488 # rest of the settings
489 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
490 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
491 config.write("name = '%s'\n" % instance.name)
494 for nic in instance.nics:
495 nic_str = "mac=%s" % (nic.mac)
496 ip = getattr(nic, "ip", None)
498 nic_str += ", ip=%s" % ip
499 vif_data.append("'%s'" % nic_str)
500 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
501 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
503 disk_data = cls._GetConfigFileDiskData(block_devices)
505 config.write("vif = [%s]\n" % ",".join(vif_data))
506 config.write("disk = [%s]\n" % ",".join(disk_data))
508 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
509 config.write("on_poweroff = 'destroy'\n")
510 config.write("on_reboot = 'restart'\n")
511 config.write("on_crash = 'restart'\n")
512 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
513 # just in case it exists
514 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
516 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
517 except EnvironmentError, err:
518 raise errors.HypervisorError("Cannot write Xen instance confile"
519 " file /etc/xen/%s: %s" %
520 (instance.name, err))
525 class XenHvmHypervisor(XenHypervisor):
526 """Xen HVM hypervisor interface"""
528 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
529 constants.VNC_PASSWORD_FILE,
533 constants.HV_ACPI: hv_base.NO_CHECK,
534 constants.HV_BOOT_ORDER: (True, ) +
535 (lambda x: x and len(x.strip("acdn")) == 0,
536 "Invalid boot order specified, must be one or more of [acdn]",
538 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
539 constants.HV_DISK_TYPE:
540 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
541 constants.HV_NIC_TYPE:
542 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
543 constants.HV_PAE: hv_base.NO_CHECK,
544 constants.HV_VNC_BIND_ADDRESS:
545 (False, utils.IsValidIP,
546 "VNC bind address is not a valid IP address", None, None),
547 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
548 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
549 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
550 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
551 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
555 def _WriteConfigFile(cls, instance, block_devices):
556 """Create a Xen 3.1 HVM config file.
559 hvp = instance.hvparams
562 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
565 kpath = hvp[constants.HV_KERNEL_PATH]
566 config.write("kernel = '%s'\n" % kpath)
568 config.write("builder = 'hvm'\n")
569 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
570 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
571 config.write("name = '%s'\n" % instance.name)
572 if hvp[constants.HV_PAE]:
573 config.write("pae = 1\n")
575 config.write("pae = 0\n")
576 if hvp[constants.HV_ACPI]:
577 config.write("acpi = 1\n")
579 config.write("acpi = 0\n")
580 config.write("apic = 1\n")
581 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
582 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
583 config.write("sdl = 0\n")
584 config.write("usb = 1\n")
585 config.write("usbdevice = 'tablet'\n")
586 config.write("vnc = 1\n")
587 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
588 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
590 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
592 if instance.network_port > constants.VNC_BASE_PORT:
593 display = instance.network_port - constants.VNC_BASE_PORT
594 config.write("vncdisplay = %s\n" % display)
595 config.write("vncunused = 0\n")
597 config.write("# vncdisplay = 1\n")
598 config.write("vncunused = 1\n")
600 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
602 password = utils.ReadFile(vnc_pwd_file)
603 except EnvironmentError, err:
604 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
607 config.write("vncpasswd = '%s'\n" % password.rstrip())
609 config.write("serial = 'pty'\n")
610 if hvp[constants.HV_USE_LOCALTIME]:
611 config.write("localtime = 1\n")
614 nic_type = hvp[constants.HV_NIC_TYPE]
616 # ensure old instances don't change
617 nic_type_str = ", type=ioemu"
618 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
619 nic_type_str = ", type=paravirtualized"
621 nic_type_str = ", model=%s, type=ioemu" % nic_type
622 for nic in instance.nics:
623 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
624 ip = getattr(nic, "ip", None)
626 nic_str += ", ip=%s" % ip
627 vif_data.append("'%s'" % nic_str)
628 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
629 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
631 config.write("vif = [%s]\n" % ",".join(vif_data))
632 disk_data = cls._GetConfigFileDiskData(block_devices)
633 disk_type = hvp[constants.HV_DISK_TYPE]
634 if disk_type in (None, constants.HT_DISK_IOEMU):
635 replacement = ",ioemu:hd"
638 disk_data = [line.replace(",sd", replacement) for line in disk_data]
639 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
641 iso = "'file:%s,hdc:cdrom,r'" % iso_path
642 disk_data.append(iso)
644 config.write("disk = [%s]\n" % (",".join(disk_data)))
646 config.write("on_poweroff = 'destroy'\n")
647 config.write("on_reboot = 'restart'\n")
648 config.write("on_crash = 'restart'\n")
649 # just in case it exists
650 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
652 utils.WriteFile("/etc/xen/%s" % instance.name,
653 data=config.getvalue())
654 except EnvironmentError, err:
655 raise errors.HypervisorError("Cannot write Xen instance confile"
656 " file /etc/xen/%s: %s" %
657 (instance.name, err))