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
30 from cStringIO import StringIO
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti.hypervisor import hv_base
38 class XenHypervisor(hv_base.BaseHypervisor):
39 """Xen generic hypervisor interface
41 This is the Xen base class used for both Xen PVM and HVM. It contains
42 all the functionality that is identical for both.
45 REBOOT_RETRY_COUNT = 60
46 REBOOT_RETRY_INTERVAL = 10
49 def _WriteConfigFile(cls, instance, block_devices):
50 """Write the Xen config file for the instance.
53 raise NotImplementedError
56 def _WriteConfigFileStatic(instance_name, data):
57 """Write the Xen config file for the instance.
59 This version of the function just writes the config file from static data.
62 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
65 def _ReadConfigFile(instance_name):
66 """Returns the contents of the instance config file.
70 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
71 except EnvironmentError, err:
72 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
76 def _RemoveConfigFile(instance_name):
77 """Remove the xen configuration file.
80 utils.RemoveFile("/etc/xen/%s" % instance_name)
83 def _GetXMList(include_node):
84 """Return the list of running instances.
86 If the include_node argument is True, then we return information
87 for dom0 also, otherwise we filter that from the return value.
89 @return: list of (name, id, memory, vcpus, state, time spent)
92 for dummy in range(5):
93 result = utils.RunCmd(["xm", "list"])
96 logging.error("xm list failed (%s): %s", result.fail_reason,
101 raise errors.HypervisorError("xm list failed, retries"
102 " exceeded (%s): %s" %
103 (result.fail_reason, result.output))
105 # skip over the heading
106 lines = result.stdout.splitlines()[1:]
109 # The format of lines is:
110 # Name ID Mem(MiB) VCPUs State Time(s)
111 # Domain-0 0 3418 4 r----- 266.2
114 raise errors.HypervisorError("Can't parse output of xm list,"
117 data[1] = int(data[1])
118 data[2] = int(data[2])
119 data[3] = int(data[3])
120 data[5] = float(data[5])
121 except ValueError, err:
122 raise errors.HypervisorError("Can't parse output of xm list,"
123 " line: %s, error: %s" % (line, err))
125 # skip the Domain-0 (optional)
126 if include_node or data[0] != 'Domain-0':
131 def ListInstances(self):
132 """Get the list of running instances.
135 xm_list = self._GetXMList(False)
136 names = [info[0] for info in xm_list]
139 def GetInstanceInfo(self, instance_name):
140 """Get instance properties.
142 @param instance_name: the instance name
144 @return: tuple (name, id, memory, vcpus, stat, times)
147 xm_list = self._GetXMList(instance_name=="Domain-0")
150 if data[0] == instance_name:
155 def GetAllInstancesInfo(self):
156 """Get properties of all instances.
158 @return: list of tuples (name, id, memory, vcpus, stat, times)
161 xm_list = self._GetXMList(False)
164 def StartInstance(self, instance, block_devices):
165 """Start an instance.
168 self._WriteConfigFile(instance, block_devices)
169 result = utils.RunCmd(["xm", "create", instance.name])
172 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
173 (instance.name, result.fail_reason,
176 def StopInstance(self, instance, force=False):
180 self._RemoveConfigFile(instance.name)
182 command = ["xm", "destroy", instance.name]
184 command = ["xm", "shutdown", instance.name]
185 result = utils.RunCmd(command)
188 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
189 (instance.name, result.fail_reason,
192 def RebootInstance(self, instance):
193 """Reboot an instance.
196 ini_info = self.GetInstanceInfo(instance.name)
197 result = utils.RunCmd(["xm", "reboot", instance.name])
200 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
201 (instance.name, result.fail_reason,
204 retries = self.REBOOT_RETRY_COUNT
206 new_info = self.GetInstanceInfo(instance.name)
207 # check if the domain ID has changed or the run time has
209 if new_info[1] != ini_info[1] or new_info[5] < ini_info[5]:
212 time.sleep(self.REBOOT_RETRY_INTERVAL)
216 raise errors.HypervisorError("Failed to reboot instance %s: instance"
217 " did not reboot in the expected interval" %
220 def GetNodeInfo(self):
221 """Return information about the node.
223 @return: a dict with the following keys (memory values in MiB):
224 - memory_total: the total memory size on the node
225 - memory_free: the available memory on the node for instances
226 - memory_dom0: the memory used by the node itself, if available
227 - nr_cpus: total number of CPUs
228 - nr_nodes: in a NUMA system, the number of domains
229 - nr_sockets: the number of physical CPU sockets in the node
232 # note: in xen 3, memory has changed to total_memory
233 result = utils.RunCmd(["xm", "info"])
235 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
239 xmoutput = result.stdout.splitlines()
241 cores_per_socket = threads_per_core = nr_cpus = None
242 for line in xmoutput:
243 splitfields = line.split(":", 1)
245 if len(splitfields) > 1:
246 key = splitfields[0].strip()
247 val = splitfields[1].strip()
248 if key == 'memory' or key == 'total_memory':
249 result['memory_total'] = int(val)
250 elif key == 'free_memory':
251 result['memory_free'] = int(val)
252 elif key == 'nr_cpus':
253 nr_cpus = result['cpu_total'] = int(val)
254 elif key == 'nr_nodes':
255 result['cpu_nodes'] = int(val)
256 elif key == 'cores_per_socket':
257 cores_per_socket = int(val)
258 elif key == 'threads_per_core':
259 threads_per_core = int(val)
261 if (cores_per_socket is not None and
262 threads_per_core is not None and nr_cpus is not None):
263 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
265 dom0_info = self.GetInstanceInfo("Domain-0")
266 if dom0_info is not None:
267 result['memory_dom0'] = dom0_info[2]
272 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
273 """Return a command for connecting to the console of an instance.
276 return "xm console %s" % instance.name
280 """Verify the hypervisor.
282 For Xen, this verifies that the xend process is running.
285 result = utils.RunCmd(["xm", "info"])
287 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
290 def _GetConfigFileDiskData(disk_template, block_devices):
291 """Get disk directive for xen config file.
293 This method builds the xen config disk directive according to the
294 given disk_template and block_devices.
296 @param disk_template: string containing instance disk template
297 @param block_devices: list of tuples (cfdev, rldev):
298 - cfdev: dict containing ganeti config disk part
299 - rldev: ganeti.bdev.BlockDev object
301 @return: string containing disk directive for xen instance config file
305 constants.FD_LOOP: "file",
306 constants.FD_BLKTAP: "tap:aio",
309 if len(block_devices) > 24:
311 raise errors.HypervisorError("Too many disks")
312 # FIXME: instead of this hardcoding here, each of PVM/HVM should
313 # directly export their info (currently HVM will just sed this info)
314 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
315 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
316 if cfdev.mode == constants.DISK_RDWR:
320 if cfdev.dev_type == constants.LD_FILE:
321 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
322 dev_path, sd_name, mode)
324 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
325 disk_data.append(line)
329 def MigrationInfo(self, instance):
330 """Get instance information to perform a migration.
332 @type instance: L{objects.Instance}
333 @param instance: instance to be migrated
335 @return: content of the xen config file
338 return self._ReadConfigFile(instance.name)
340 def AcceptInstance(self, instance, info, target):
341 """Prepare to accept an instance.
343 @type instance: L{objects.Instance}
344 @param instance: instance to be accepted
346 @param info: content of the xen config file on the source node
348 @param target: target host (usually ip), on this node
353 def FinalizeMigration(self, instance, info, success):
354 """Finalize an instance migration.
356 After a successful migration we write the xen config file.
357 We do nothing on a failure, as we did not change anything at accept time.
359 @type instance: L{objects.Instance}
360 @param instance: instance whose migration is being aborted
362 @param info: content of the xen config file on the source node
363 @type success: boolean
364 @param success: whether the migration was a success or a failure
368 self._WriteConfigFileStatic(instance.name, info)
370 def MigrateInstance(self, instance, target, live):
371 """Migrate an instance to a target node.
373 The migration will not be attempted if the instance is not
376 @type instance: string
377 @param instance: instance name
379 @param target: ip address of the target node
381 @param live: perform a live migration
384 if self.GetInstanceInfo(instance) is None:
385 raise errors.HypervisorError("Instance not running, cannot migrate")
386 args = ["xm", "migrate"]
389 args.extend([instance, target])
390 result = utils.RunCmd(args)
392 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
393 (instance, result.output))
394 # remove old xen file after migration succeeded
396 self._RemoveConfigFile(instance)
397 except EnvironmentError:
398 logging.exception("Failure while removing instance config file")
401 class XenPvmHypervisor(XenHypervisor):
402 """Xen PVM hypervisor interface"""
405 constants.HV_KERNEL_PATH,
406 constants.HV_INITRD_PATH,
407 constants.HV_ROOT_PATH,
408 constants.HV_KERNEL_ARGS,
412 def CheckParameterSyntax(cls, hvparams):
413 """Check the given parameters for validity.
415 For the PVM hypervisor, this only check the existence of the
419 @param hvparams: dictionary with parameter names/value
420 @raise errors.HypervisorError: when a parameter is not valid
423 super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
425 if not hvparams[constants.HV_KERNEL_PATH]:
426 raise errors.HypervisorError("Need a kernel for the instance")
428 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
429 raise errors.HypervisorError("The kernel path must be an absolute path")
431 if not hvparams[constants.HV_ROOT_PATH]:
432 raise errors.HypervisorError("Need a root partition for the instance")
434 if hvparams[constants.HV_INITRD_PATH]:
435 if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
436 raise errors.HypervisorError("The initrd path must be an absolute path"
439 def ValidateParameters(self, hvparams):
440 """Check the given parameters for validity.
442 For the PVM hypervisor, this only check the existence of the
446 super(XenPvmHypervisor, self).ValidateParameters(hvparams)
448 kernel_path = hvparams[constants.HV_KERNEL_PATH]
449 if not os.path.isfile(kernel_path):
450 raise errors.HypervisorError("Instance kernel '%s' not found or"
451 " not a file" % kernel_path)
452 initrd_path = hvparams[constants.HV_INITRD_PATH]
453 if initrd_path and not os.path.isfile(initrd_path):
454 raise errors.HypervisorError("Instance initrd '%s' not found or"
455 " not a file" % initrd_path)
458 def _WriteConfigFile(cls, instance, block_devices):
459 """Write the Xen config file for the instance.
462 hvp = instance.hvparams
464 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
467 kpath = hvp[constants.HV_KERNEL_PATH]
468 config.write("kernel = '%s'\n" % kpath)
471 initrd_path = hvp[constants.HV_INITRD_PATH]
473 config.write("ramdisk = '%s'\n" % initrd_path)
475 # rest of the settings
476 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
477 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
478 config.write("name = '%s'\n" % instance.name)
481 for nic in instance.nics:
482 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
483 ip = getattr(nic, "ip", None)
485 nic_str += ", ip=%s" % ip
486 vif_data.append("'%s'" % nic_str)
488 config.write("vif = [%s]\n" % ",".join(vif_data))
489 config.write("disk = [%s]\n" % ",".join(
490 cls._GetConfigFileDiskData(instance.disk_template,
493 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
494 config.write("on_poweroff = 'destroy'\n")
495 config.write("on_reboot = 'restart'\n")
496 config.write("on_crash = 'restart'\n")
497 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
498 # just in case it exists
499 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
501 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
502 except EnvironmentError, err:
503 raise errors.HypervisorError("Cannot write Xen instance confile"
504 " file /etc/xen/%s: %s" %
505 (instance.name, err))
510 class XenHvmHypervisor(XenHypervisor):
511 """Xen HVM hypervisor interface"""
515 constants.HV_BOOT_ORDER,
516 constants.HV_CDROM_IMAGE_PATH,
517 constants.HV_DISK_TYPE,
518 constants.HV_NIC_TYPE,
520 constants.HV_VNC_BIND_ADDRESS,
521 constants.HV_KERNEL_PATH,
522 constants.HV_DEVICE_MODEL,
526 def CheckParameterSyntax(cls, hvparams):
527 """Check the given parameter syntax.
530 super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
531 # boot order verification
532 boot_order = hvparams[constants.HV_BOOT_ORDER]
533 if not boot_order or len(boot_order.strip("acdn")) != 0:
534 raise errors.HypervisorError("Invalid boot order '%s' specified,"
535 " must be one or more of [acdn]" %
538 nic_type = hvparams[constants.HV_NIC_TYPE]
539 if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
540 raise errors.HypervisorError(\
541 "Invalid NIC type %s specified for the Xen"
542 " HVM hypervisor. Please choose one of: %s"
543 % (nic_type, utils.CommaJoin(constants.HT_HVM_VALID_NIC_TYPES)))
544 disk_type = hvparams[constants.HV_DISK_TYPE]
545 if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
546 raise errors.HypervisorError(\
547 "Invalid disk type %s specified for the Xen"
548 " HVM hypervisor. Please choose one of: %s"
549 % (disk_type, utils.CommaJoin(constants.HT_HVM_VALID_DISK_TYPES)))
550 # vnc_bind_address verification
551 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
553 if not utils.IsValidIP(vnc_bind_address):
554 raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
555 " like a valid IP address" %
558 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
559 if iso_path and not os.path.isabs(iso_path):
560 raise errors.HypervisorError("The path to the HVM CDROM image must"
561 " be an absolute path or None, not %s" %
564 if not hvparams[constants.HV_KERNEL_PATH]:
565 raise errors.HypervisorError("Need a kernel for the instance")
567 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
568 raise errors.HypervisorError("The kernel path must be an absolute path")
570 if not hvparams[constants.HV_DEVICE_MODEL]:
571 raise errors.HypervisorError("Need a device model for the instance")
573 if not os.path.isabs(hvparams[constants.HV_DEVICE_MODEL]):
574 raise errors.HypervisorError("The device model must be an absolute path")
577 def ValidateParameters(self, hvparams):
578 """Check the given parameters for validity.
580 For the PVM hypervisor, this only check the existence of the
584 @param hvparams: dictionary with parameter names/value
585 @raise errors.HypervisorError: when a parameter is not valid
588 super(XenHvmHypervisor, self).ValidateParameters(hvparams)
590 # hvm_cdrom_image_path verification
591 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
592 if iso_path and not os.path.isfile(iso_path):
593 raise errors.HypervisorError("The HVM CDROM image must either be a"
594 " regular file or a symlink pointing to"
595 " an existing regular file, not %s" %
598 kernel_path = hvparams[constants.HV_KERNEL_PATH]
599 if not os.path.isfile(kernel_path):
600 raise errors.HypervisorError("Instance kernel '%s' not found or"
601 " not a file" % kernel_path)
603 device_model = hvparams[constants.HV_DEVICE_MODEL]
604 if not os.path.isfile(device_model):
605 raise errors.HypervisorError("Device model '%s' not found or"
606 " not a file" % device_model)
609 def _WriteConfigFile(cls, instance, block_devices):
610 """Create a Xen 3.1 HVM config file.
613 hvp = instance.hvparams
616 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
619 kpath = hvp[constants.HV_KERNEL_PATH]
620 config.write("kernel = '%s'\n" % kpath)
622 config.write("builder = 'hvm'\n")
623 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
624 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
625 config.write("name = '%s'\n" % instance.name)
626 if hvp[constants.HV_PAE]:
627 config.write("pae = 1\n")
629 config.write("pae = 0\n")
630 if hvp[constants.HV_ACPI]:
631 config.write("acpi = 1\n")
633 config.write("acpi = 0\n")
634 config.write("apic = 1\n")
635 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
636 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
637 config.write("sdl = 0\n")
638 config.write("usb = 1\n")
639 config.write("usbdevice = 'tablet'\n")
640 config.write("vnc = 1\n")
641 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
642 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
644 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
646 if instance.network_port > constants.VNC_BASE_PORT:
647 display = instance.network_port - constants.VNC_BASE_PORT
648 config.write("vncdisplay = %s\n" % display)
649 config.write("vncunused = 0\n")
651 config.write("# vncdisplay = 1\n")
652 config.write("vncunused = 1\n")
655 password = utils.ReadFile(constants.VNC_PASSWORD_FILE)
656 except EnvironmentError, err:
657 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
658 (constants.VNC_PASSWORD_FILE, err))
660 config.write("vncpasswd = '%s'\n" % password.rstrip())
662 config.write("serial = 'pty'\n")
663 config.write("localtime = 1\n")
666 nic_type = hvp[constants.HV_NIC_TYPE]
668 # ensure old instances don't change
669 nic_type_str = ", type=ioemu"
670 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
671 nic_type_str = ", type=paravirtualized"
673 nic_type_str = ", model=%s, type=ioemu" % nic_type
674 for nic in instance.nics:
675 nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
676 ip = getattr(nic, "ip", None)
678 nic_str += ", ip=%s" % ip
679 vif_data.append("'%s'" % nic_str)
681 config.write("vif = [%s]\n" % ",".join(vif_data))
682 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
684 disk_type = hvp[constants.HV_DISK_TYPE]
685 if disk_type in (None, constants.HT_DISK_IOEMU):
686 replacement = ",ioemu:hd"
689 disk_data = [line.replace(",sd", replacement) for line in disk_data]
690 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
692 iso = "'file:%s,hdc:cdrom,r'" % iso_path
693 disk_data.append(iso)
695 config.write("disk = [%s]\n" % (",".join(disk_data)))
697 config.write("on_poweroff = 'destroy'\n")
698 config.write("on_reboot = 'restart'\n")
699 config.write("on_crash = 'restart'\n")
700 # just in case it exists
701 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
703 utils.WriteFile("/etc/xen/%s" % instance.name,
704 data=config.getvalue())
705 except EnvironmentError, err:
706 raise errors.HypervisorError("Cannot write Xen instance confile"
707 " file /etc/xen/%s: %s" %
708 (instance.name, err))