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, name=None):
198 self._RemoveConfigFile(name)
200 command = ["xm", "destroy", name]
202 command = ["xm", "shutdown", name]
203 result = utils.RunCmd(command)
206 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
207 (name, result.fail_reason, result.output))
209 def RebootInstance(self, instance):
210 """Reboot an instance.
213 ini_info = self.GetInstanceInfo(instance.name)
215 result = utils.RunCmd(["xm", "reboot", instance.name])
217 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
218 (instance.name, result.fail_reason,
221 def _CheckInstance():
222 new_info = self.GetInstanceInfo(instance.name)
224 # check if the domain ID has changed or the run time has decreased
225 if new_info[1] != ini_info[1] or new_info[5] < ini_info[5]:
228 raise utils.RetryAgain()
231 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
232 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
233 except utils.RetryTimeout:
234 raise errors.HypervisorError("Failed to reboot instance %s: instance"
235 " did not reboot in the expected interval" %
238 def GetNodeInfo(self):
239 """Return information about the node.
241 @return: a dict with the following keys (memory values in MiB):
242 - memory_total: the total memory size on the node
243 - memory_free: the available memory on the node for instances
244 - memory_dom0: the memory used by the node itself, if available
245 - nr_cpus: total number of CPUs
246 - nr_nodes: in a NUMA system, the number of domains
247 - nr_sockets: the number of physical CPU sockets in the node
250 # note: in xen 3, memory has changed to total_memory
251 result = utils.RunCmd(["xm", "info"])
253 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
257 xmoutput = result.stdout.splitlines()
259 cores_per_socket = threads_per_core = nr_cpus = None
260 for line in xmoutput:
261 splitfields = line.split(":", 1)
263 if len(splitfields) > 1:
264 key = splitfields[0].strip()
265 val = splitfields[1].strip()
266 if key == 'memory' or key == 'total_memory':
267 result['memory_total'] = int(val)
268 elif key == 'free_memory':
269 result['memory_free'] = int(val)
270 elif key == 'nr_cpus':
271 nr_cpus = result['cpu_total'] = int(val)
272 elif key == 'nr_nodes':
273 result['cpu_nodes'] = int(val)
274 elif key == 'cores_per_socket':
275 cores_per_socket = int(val)
276 elif key == 'threads_per_core':
277 threads_per_core = int(val)
279 if (cores_per_socket is not None and
280 threads_per_core is not None and nr_cpus is not None):
281 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
283 dom0_info = self.GetInstanceInfo("Domain-0")
284 if dom0_info is not None:
285 result['memory_dom0'] = dom0_info[2]
290 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
291 """Return a command for connecting to the console of an instance.
294 return "xm console %s" % instance.name
298 """Verify the hypervisor.
300 For Xen, this verifies that the xend process is running.
303 result = utils.RunCmd(["xm", "info"])
305 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
308 def _GetConfigFileDiskData(block_devices):
309 """Get disk directive for xen config file.
311 This method builds the xen config disk directive according to the
312 given disk_template and block_devices.
314 @param block_devices: list of tuples (cfdev, rldev):
315 - cfdev: dict containing ganeti config disk part
316 - rldev: ganeti.bdev.BlockDev object
318 @return: string containing disk directive for xen instance config file
322 constants.FD_LOOP: "file",
323 constants.FD_BLKTAP: "tap:aio",
326 if len(block_devices) > 24:
328 raise errors.HypervisorError("Too many disks")
329 # FIXME: instead of this hardcoding here, each of PVM/HVM should
330 # directly export their info (currently HVM will just sed this info)
331 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
332 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
333 if cfdev.mode == constants.DISK_RDWR:
337 if cfdev.dev_type == constants.LD_FILE:
338 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
339 dev_path, sd_name, mode)
341 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
342 disk_data.append(line)
346 def MigrationInfo(self, instance):
347 """Get instance information to perform a migration.
349 @type instance: L{objects.Instance}
350 @param instance: instance to be migrated
352 @return: content of the xen config file
355 return self._ReadConfigFile(instance.name)
357 def AcceptInstance(self, instance, info, target):
358 """Prepare to accept an instance.
360 @type instance: L{objects.Instance}
361 @param instance: instance to be accepted
363 @param info: content of the xen config file on the source node
365 @param target: target host (usually ip), on this node
370 def FinalizeMigration(self, instance, info, success):
371 """Finalize an instance migration.
373 After a successful migration we write the xen config file.
374 We do nothing on a failure, as we did not change anything at accept time.
376 @type instance: L{objects.Instance}
377 @param instance: instance whose migration is being aborted
379 @param info: content of the xen config file on the source node
380 @type success: boolean
381 @param success: whether the migration was a success or a failure
385 self._WriteConfigFileStatic(instance.name, info)
387 def MigrateInstance(self, instance, target, live):
388 """Migrate an instance to a target node.
390 The migration will not be attempted if the instance is not
393 @type instance: L{objects.Instance}
394 @param instance: the instance to be migrated
396 @param target: ip address of the target node
398 @param live: perform a live migration
401 if self.GetInstanceInfo(instance.name) is None:
402 raise errors.HypervisorError("Instance not running, cannot migrate")
404 port = instance.hvparams[constants.HV_MIGRATION_PORT]
406 if not utils.TcpPing(target, port, live_port_needed=True):
407 raise errors.HypervisorError("Remote host %s not listening on port"
408 " %s, cannot migrate" % (target, port))
410 args = ["xm", "migrate", "-p", "%d" % port]
413 args.extend([instance.name, target])
414 result = utils.RunCmd(args)
416 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
417 (instance.name, result.output))
418 # remove old xen file after migration succeeded
420 self._RemoveConfigFile(instance.name)
421 except EnvironmentError:
422 logging.exception("Failure while removing instance config file")
425 def PowercycleNode(cls):
426 """Xen-specific powercycle.
428 This first does a Linux reboot (which triggers automatically a Xen
429 reboot), and if that fails it tries to do a Xen reboot. The reason
430 we don't try a Xen reboot first is that the xen reboot launches an
431 external command which connects to the Xen hypervisor, and that
432 won't work in case the root filesystem is broken and/or the xend
433 daemon is not working.
437 cls.LinuxPowercycle()
439 utils.RunCmd(["xm", "debug", "R"])
442 class XenPvmHypervisor(XenHypervisor):
443 """Xen PVM hypervisor interface"""
446 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
447 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
448 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
449 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
450 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
451 constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK,
452 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
453 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
457 def _WriteConfigFile(cls, instance, block_devices):
458 """Write the Xen config file for the instance.
461 hvp = instance.hvparams
463 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
465 # if bootloader is True, use bootloader instead of kernel and ramdisk
467 if hvp[constants.HV_USE_BOOTLOADER]:
468 # bootloader handling
469 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
471 config.write("bootloader = '%s'\n" % bootloader_path)
473 raise errors.HypervisorError("Bootloader enabled, but missing"
476 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
478 config.write("bootargs = '%s'\n" % bootloader_args)
481 kpath = hvp[constants.HV_KERNEL_PATH]
482 config.write("kernel = '%s'\n" % kpath)
485 initrd_path = hvp[constants.HV_INITRD_PATH]
487 config.write("ramdisk = '%s'\n" % initrd_path)
489 # rest of the settings
490 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
491 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
492 config.write("name = '%s'\n" % instance.name)
495 for nic in instance.nics:
496 nic_str = "mac=%s" % (nic.mac)
497 ip = getattr(nic, "ip", None)
499 nic_str += ", ip=%s" % ip
500 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
501 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
502 vif_data.append("'%s'" % nic_str)
504 disk_data = cls._GetConfigFileDiskData(block_devices)
506 config.write("vif = [%s]\n" % ",".join(vif_data))
507 config.write("disk = [%s]\n" % ",".join(disk_data))
509 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
510 config.write("on_poweroff = 'destroy'\n")
511 config.write("on_reboot = 'restart'\n")
512 config.write("on_crash = 'restart'\n")
513 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
514 # just in case it exists
515 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
517 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
518 except EnvironmentError, err:
519 raise errors.HypervisorError("Cannot write Xen instance confile"
520 " file /etc/xen/%s: %s" %
521 (instance.name, err))
526 class XenHvmHypervisor(XenHypervisor):
527 """Xen HVM hypervisor interface"""
529 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
530 constants.VNC_PASSWORD_FILE,
534 constants.HV_ACPI: hv_base.NO_CHECK,
535 constants.HV_BOOT_ORDER: (True, ) +
536 (lambda x: x and len(x.strip("acdn")) == 0,
537 "Invalid boot order specified, must be one or more of [acdn]",
539 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
540 constants.HV_DISK_TYPE:
541 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
542 constants.HV_NIC_TYPE:
543 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
544 constants.HV_PAE: hv_base.NO_CHECK,
545 constants.HV_VNC_BIND_ADDRESS:
546 (False, utils.IsValidIP,
547 "VNC bind address is not a valid IP address", None, None),
548 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
549 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
550 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
551 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
552 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
556 def _WriteConfigFile(cls, instance, block_devices):
557 """Create a Xen 3.1 HVM config file.
560 hvp = instance.hvparams
563 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
566 kpath = hvp[constants.HV_KERNEL_PATH]
567 config.write("kernel = '%s'\n" % kpath)
569 config.write("builder = 'hvm'\n")
570 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
571 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
572 config.write("name = '%s'\n" % instance.name)
573 if hvp[constants.HV_PAE]:
574 config.write("pae = 1\n")
576 config.write("pae = 0\n")
577 if hvp[constants.HV_ACPI]:
578 config.write("acpi = 1\n")
580 config.write("acpi = 0\n")
581 config.write("apic = 1\n")
582 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
583 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
584 config.write("sdl = 0\n")
585 config.write("usb = 1\n")
586 config.write("usbdevice = 'tablet'\n")
587 config.write("vnc = 1\n")
588 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
589 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
591 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
593 if instance.network_port > constants.VNC_BASE_PORT:
594 display = instance.network_port - constants.VNC_BASE_PORT
595 config.write("vncdisplay = %s\n" % display)
596 config.write("vncunused = 0\n")
598 config.write("# vncdisplay = 1\n")
599 config.write("vncunused = 1\n")
601 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
603 password = utils.ReadFile(vnc_pwd_file)
604 except EnvironmentError, err:
605 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
608 config.write("vncpasswd = '%s'\n" % password.rstrip())
610 config.write("serial = 'pty'\n")
611 if hvp[constants.HV_USE_LOCALTIME]:
612 config.write("localtime = 1\n")
615 nic_type = hvp[constants.HV_NIC_TYPE]
617 # ensure old instances don't change
618 nic_type_str = ", type=ioemu"
619 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
620 nic_type_str = ", type=paravirtualized"
622 nic_type_str = ", model=%s, type=ioemu" % nic_type
623 for nic in instance.nics:
624 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
625 ip = getattr(nic, "ip", None)
627 nic_str += ", ip=%s" % ip
628 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
629 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
630 vif_data.append("'%s'" % nic_str)
632 config.write("vif = [%s]\n" % ",".join(vif_data))
633 disk_data = cls._GetConfigFileDiskData(block_devices)
634 disk_type = hvp[constants.HV_DISK_TYPE]
635 if disk_type in (None, constants.HT_DISK_IOEMU):
636 replacement = ",ioemu:hd"
639 disk_data = [line.replace(",sd", replacement) for line in disk_data]
640 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
642 iso = "'file:%s,hdc:cdrom,r'" % iso_path
643 disk_data.append(iso)
645 config.write("disk = [%s]\n" % (",".join(disk_data)))
647 config.write("on_poweroff = 'destroy'\n")
648 config.write("on_reboot = 'restart'\n")
649 config.write("on_crash = 'restart'\n")
650 # just in case it exists
651 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
653 utils.WriteFile("/etc/xen/%s" % instance.name,
654 data=config.getvalue())
655 except EnvironmentError, err:
656 raise errors.HypervisorError("Cannot write Xen instance confile"
657 " file /etc/xen/%s: %s" %
658 (instance.name, err))