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
36 class XenHypervisor(hv_base.BaseHypervisor):
37 """Xen generic hypervisor interface
39 This is the Xen base class used for both Xen PVM and HVM. It contains
40 all the functionality that is identical for both.
44 REBOOT_RETRY_COUNT = 60
45 REBOOT_RETRY_INTERVAL = 10
48 '/etc/xen/xend-config.sxp',
49 '/etc/xen/scripts/vif-bridge',
53 def _WriteConfigFile(cls, instance, block_devices):
54 """Write the Xen config file for the instance.
57 raise NotImplementedError
60 def _WriteConfigFileStatic(instance_name, data):
61 """Write the Xen config file for the instance.
63 This version of the function just writes the config file from static data.
66 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
69 def _ReadConfigFile(instance_name):
70 """Returns the contents of the instance config file.
74 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
75 except EnvironmentError, err:
76 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
80 def _RemoveConfigFile(instance_name):
81 """Remove the xen configuration file.
84 utils.RemoveFile("/etc/xen/%s" % instance_name)
87 def _RunXmList(xmlist_errors):
88 """Helper function for L{_GetXMList} to run "xm list".
91 result = utils.RunCmd(["xm", "list"])
93 logging.error("xm list failed (%s): %s", result.fail_reason,
95 xmlist_errors.append(result)
96 raise utils.RetryAgain()
98 # skip over the heading
99 return result.stdout.splitlines()[1:]
102 def _GetXMList(cls, include_node):
103 """Return the list of running instances.
105 If the include_node argument is True, then we return information
106 for dom0 also, otherwise we filter that from the return value.
108 @return: list of (name, id, memory, vcpus, state, time spent)
113 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
114 except utils.RetryTimeout:
116 xmlist_result = xmlist_errors.pop()
118 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
119 (xmlist_result.fail_reason, xmlist_result.output))
121 errmsg = "xm list failed"
123 raise errors.HypervisorError(errmsg)
127 # The format of lines is:
128 # Name ID Mem(MiB) VCPUs State Time(s)
129 # Domain-0 0 3418 4 r----- 266.2
132 raise errors.HypervisorError("Can't parse output of xm list,"
135 data[1] = int(data[1])
136 data[2] = int(data[2])
137 data[3] = int(data[3])
138 data[5] = float(data[5])
139 except (TypeError, ValueError), err:
140 raise errors.HypervisorError("Can't parse output of xm list,"
141 " line: %s, error: %s" % (line, err))
143 # skip the Domain-0 (optional)
144 if include_node or data[0] != 'Domain-0':
149 def ListInstances(self):
150 """Get the list of running instances.
153 xm_list = self._GetXMList(False)
154 names = [info[0] for info in xm_list]
157 def GetInstanceInfo(self, instance_name):
158 """Get instance properties.
160 @param instance_name: the instance name
162 @return: tuple (name, id, memory, vcpus, stat, times)
165 xm_list = self._GetXMList(instance_name=="Domain-0")
168 if data[0] == instance_name:
173 def GetAllInstancesInfo(self):
174 """Get properties of all instances.
176 @return: list of tuples (name, id, memory, vcpus, stat, times)
179 xm_list = self._GetXMList(False)
182 def StartInstance(self, instance, block_devices):
183 """Start an instance.
186 self._WriteConfigFile(instance, block_devices)
187 result = utils.RunCmd(["xm", "create", instance.name])
190 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
191 (instance.name, result.fail_reason,
194 def StopInstance(self, instance, force=False, retry=False, name=None):
200 self._RemoveConfigFile(name)
202 command = ["xm", "destroy", name]
204 command = ["xm", "shutdown", name]
205 result = utils.RunCmd(command)
208 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
209 (name, result.fail_reason, result.output))
211 def RebootInstance(self, instance):
212 """Reboot an instance.
215 ini_info = self.GetInstanceInfo(instance.name)
218 raise errors.HypervisorError("Failed to reboot instance %s,"
219 " not running" % instance.name)
221 result = utils.RunCmd(["xm", "reboot", instance.name])
223 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
224 (instance.name, result.fail_reason,
227 def _CheckInstance():
228 new_info = self.GetInstanceInfo(instance.name)
230 # check if the domain ID has changed or the run time has decreased
231 if (new_info is not None and
232 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
235 raise utils.RetryAgain()
238 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
239 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
240 except utils.RetryTimeout:
241 raise errors.HypervisorError("Failed to reboot instance %s: instance"
242 " did not reboot in the expected interval" %
245 def GetNodeInfo(self):
246 """Return information about the node.
248 @return: a dict with the following keys (memory values in MiB):
249 - memory_total: the total memory size on the node
250 - memory_free: the available memory on the node for instances
251 - memory_dom0: the memory used by the node itself, if available
252 - nr_cpus: total number of CPUs
253 - nr_nodes: in a NUMA system, the number of domains
254 - nr_sockets: the number of physical CPU sockets in the node
257 # note: in xen 3, memory has changed to total_memory
258 result = utils.RunCmd(["xm", "info"])
260 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
264 xmoutput = result.stdout.splitlines()
266 cores_per_socket = threads_per_core = nr_cpus = None
267 for line in xmoutput:
268 splitfields = line.split(":", 1)
270 if len(splitfields) > 1:
271 key = splitfields[0].strip()
272 val = splitfields[1].strip()
273 if key == 'memory' or key == 'total_memory':
274 result['memory_total'] = int(val)
275 elif key == 'free_memory':
276 result['memory_free'] = int(val)
277 elif key == 'nr_cpus':
278 nr_cpus = result['cpu_total'] = int(val)
279 elif key == 'nr_nodes':
280 result['cpu_nodes'] = int(val)
281 elif key == 'cores_per_socket':
282 cores_per_socket = int(val)
283 elif key == 'threads_per_core':
284 threads_per_core = int(val)
286 if (cores_per_socket is not None and
287 threads_per_core is not None and nr_cpus is not None):
288 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
290 dom0_info = self.GetInstanceInfo("Domain-0")
291 if dom0_info is not None:
292 result['memory_dom0'] = dom0_info[2]
297 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
298 """Return a command for connecting to the console of an instance.
301 return "xm console %s" % instance.name
305 """Verify the hypervisor.
307 For Xen, this verifies that the xend process is running.
310 result = utils.RunCmd(["xm", "info"])
312 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
315 def _GetConfigFileDiskData(block_devices):
316 """Get disk directive for xen config file.
318 This method builds the xen config disk directive according to the
319 given disk_template and block_devices.
321 @param block_devices: list of tuples (cfdev, rldev):
322 - cfdev: dict containing ganeti config disk part
323 - rldev: ganeti.bdev.BlockDev object
325 @return: string containing disk directive for xen instance config file
329 constants.FD_LOOP: "file",
330 constants.FD_BLKTAP: "tap:aio",
333 if len(block_devices) > 24:
335 raise errors.HypervisorError("Too many disks")
336 # FIXME: instead of this hardcoding here, each of PVM/HVM should
337 # directly export their info (currently HVM will just sed this info)
338 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
339 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
340 if cfdev.mode == constants.DISK_RDWR:
344 if cfdev.dev_type == constants.LD_FILE:
345 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
346 dev_path, sd_name, mode)
348 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
349 disk_data.append(line)
353 def MigrationInfo(self, instance):
354 """Get instance information to perform a migration.
356 @type instance: L{objects.Instance}
357 @param instance: instance to be migrated
359 @return: content of the xen config file
362 return self._ReadConfigFile(instance.name)
364 def AcceptInstance(self, instance, info, target):
365 """Prepare to accept an instance.
367 @type instance: L{objects.Instance}
368 @param instance: instance to be accepted
370 @param info: content of the xen config file on the source node
372 @param target: target host (usually ip), on this node
377 def FinalizeMigration(self, instance, info, success):
378 """Finalize an instance migration.
380 After a successful migration we write the xen config file.
381 We do nothing on a failure, as we did not change anything at accept time.
383 @type instance: L{objects.Instance}
384 @param instance: instance whose migration is being finalized
386 @param info: content of the xen config file on the source node
387 @type success: boolean
388 @param success: whether the migration was a success or a failure
392 self._WriteConfigFileStatic(instance.name, info)
394 def MigrateInstance(self, instance, target, live):
395 """Migrate an instance to a target node.
397 The migration will not be attempted if the instance is not
400 @type instance: L{objects.Instance}
401 @param instance: the instance to be migrated
403 @param target: ip address of the target node
405 @param live: perform a live migration
408 if self.GetInstanceInfo(instance.name) is None:
409 raise errors.HypervisorError("Instance not running, cannot migrate")
411 port = instance.hvparams[constants.HV_MIGRATION_PORT]
413 if not netutils.TcpPing(target, port, live_port_needed=True):
414 raise errors.HypervisorError("Remote host %s not listening on port"
415 " %s, cannot migrate" % (target, port))
417 args = ["xm", "migrate", "-p", "%d" % port]
420 args.extend([instance.name, target])
421 result = utils.RunCmd(args)
423 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
424 (instance.name, result.output))
425 # remove old xen file after migration succeeded
427 self._RemoveConfigFile(instance.name)
428 except EnvironmentError:
429 logging.exception("Failure while removing instance config file")
432 def PowercycleNode(cls):
433 """Xen-specific powercycle.
435 This first does a Linux reboot (which triggers automatically a Xen
436 reboot), and if that fails it tries to do a Xen reboot. The reason
437 we don't try a Xen reboot first is that the xen reboot launches an
438 external command which connects to the Xen hypervisor, and that
439 won't work in case the root filesystem is broken and/or the xend
440 daemon is not working.
444 cls.LinuxPowercycle()
446 utils.RunCmd(["xm", "debug", "R"])
449 class XenPvmHypervisor(XenHypervisor):
450 """Xen PVM hypervisor interface"""
453 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
454 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
455 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
456 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
457 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
458 constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK,
459 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
460 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
461 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
465 def _WriteConfigFile(cls, instance, block_devices):
466 """Write the Xen config file for the instance.
469 hvp = instance.hvparams
471 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
473 # if bootloader is True, use bootloader instead of kernel and ramdisk
475 if hvp[constants.HV_USE_BOOTLOADER]:
476 # bootloader handling
477 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
479 config.write("bootloader = '%s'\n" % bootloader_path)
481 raise errors.HypervisorError("Bootloader enabled, but missing"
484 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
486 config.write("bootargs = '%s'\n" % bootloader_args)
489 kpath = hvp[constants.HV_KERNEL_PATH]
490 config.write("kernel = '%s'\n" % kpath)
493 initrd_path = hvp[constants.HV_INITRD_PATH]
495 config.write("ramdisk = '%s'\n" % initrd_path)
497 # rest of the settings
498 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
499 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
500 config.write("name = '%s'\n" % instance.name)
503 for nic in instance.nics:
504 nic_str = "mac=%s" % (nic.mac)
505 ip = getattr(nic, "ip", None)
507 nic_str += ", ip=%s" % ip
508 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
509 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
510 vif_data.append("'%s'" % nic_str)
512 disk_data = cls._GetConfigFileDiskData(block_devices)
514 config.write("vif = [%s]\n" % ",".join(vif_data))
515 config.write("disk = [%s]\n" % ",".join(disk_data))
517 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
518 config.write("on_poweroff = 'destroy'\n")
519 config.write("on_reboot = 'restart'\n")
520 config.write("on_crash = 'restart'\n")
521 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
522 # just in case it exists
523 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
525 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
526 except EnvironmentError, err:
527 raise errors.HypervisorError("Cannot write Xen instance confile"
528 " file /etc/xen/%s: %s" %
529 (instance.name, err))
534 class XenHvmHypervisor(XenHypervisor):
535 """Xen HVM hypervisor interface"""
537 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
538 constants.VNC_PASSWORD_FILE,
542 constants.HV_ACPI: hv_base.NO_CHECK,
543 constants.HV_BOOT_ORDER: (True, ) +
544 (lambda x: x and len(x.strip("acdn")) == 0,
545 "Invalid boot order specified, must be one or more of [acdn]",
547 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
548 constants.HV_DISK_TYPE:
549 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
550 constants.HV_NIC_TYPE:
551 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
552 constants.HV_PAE: hv_base.NO_CHECK,
553 constants.HV_VNC_BIND_ADDRESS:
554 (False, netutils.IP4Address.IsValid,
555 "VNC bind address is not a valid IP address", None, None),
556 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
557 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
558 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
559 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
560 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
561 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
565 def _WriteConfigFile(cls, instance, block_devices):
566 """Create a Xen 3.1 HVM config file.
569 hvp = instance.hvparams
572 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
575 kpath = hvp[constants.HV_KERNEL_PATH]
576 config.write("kernel = '%s'\n" % kpath)
578 config.write("builder = 'hvm'\n")
579 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
580 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
581 config.write("name = '%s'\n" % instance.name)
582 if hvp[constants.HV_PAE]:
583 config.write("pae = 1\n")
585 config.write("pae = 0\n")
586 if hvp[constants.HV_ACPI]:
587 config.write("acpi = 1\n")
589 config.write("acpi = 0\n")
590 config.write("apic = 1\n")
591 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
592 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
593 config.write("sdl = 0\n")
594 config.write("usb = 1\n")
595 config.write("usbdevice = 'tablet'\n")
596 config.write("vnc = 1\n")
597 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
598 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
600 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
602 if instance.network_port > constants.VNC_BASE_PORT:
603 display = instance.network_port - constants.VNC_BASE_PORT
604 config.write("vncdisplay = %s\n" % display)
605 config.write("vncunused = 0\n")
607 config.write("# vncdisplay = 1\n")
608 config.write("vncunused = 1\n")
610 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
612 password = utils.ReadFile(vnc_pwd_file)
613 except EnvironmentError, err:
614 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
617 config.write("vncpasswd = '%s'\n" % password.rstrip())
619 config.write("serial = 'pty'\n")
620 if hvp[constants.HV_USE_LOCALTIME]:
621 config.write("localtime = 1\n")
624 nic_type = hvp[constants.HV_NIC_TYPE]
626 # ensure old instances don't change
627 nic_type_str = ", type=ioemu"
628 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
629 nic_type_str = ", type=paravirtualized"
631 nic_type_str = ", model=%s, type=ioemu" % nic_type
632 for nic in instance.nics:
633 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
634 ip = getattr(nic, "ip", None)
636 nic_str += ", ip=%s" % ip
637 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
638 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
639 vif_data.append("'%s'" % nic_str)
641 config.write("vif = [%s]\n" % ",".join(vif_data))
642 disk_data = cls._GetConfigFileDiskData(block_devices)
643 disk_type = hvp[constants.HV_DISK_TYPE]
644 if disk_type in (None, constants.HT_DISK_IOEMU):
645 replacement = ",ioemu:hd"
648 disk_data = [line.replace(",sd", replacement) for line in disk_data]
649 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
651 iso = "'file:%s,hdc:cdrom,r'" % iso_path
652 disk_data.append(iso)
654 config.write("disk = [%s]\n" % (",".join(disk_data)))
656 config.write("on_poweroff = 'destroy'\n")
657 config.write("on_reboot = 'restart'\n")
658 config.write("on_crash = 'restart'\n")
659 # just in case it exists
660 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
662 utils.WriteFile("/etc/xen/%s" % instance.name,
663 data=config.getvalue())
664 except EnvironmentError, err:
665 raise errors.HypervisorError("Cannot write Xen instance confile"
666 " file /etc/xen/%s: %s" %
667 (instance.name, err))