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.
47 def _WriteConfigFile(cls, instance, block_devices, extra_args):
48 """Write the Xen config file for the instance.
51 raise NotImplementedError
54 def _WriteConfigFileStatic(instance_name, data):
55 """Write the Xen config file for the instance.
57 This version of the function just writes the config file from static data.
60 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
63 def _ReadConfigFile(instance_name):
64 """Returns the contents of the instance config file.
68 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
69 except EnvironmentError, err:
70 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
74 def _RemoveConfigFile(instance_name):
75 """Remove the xen configuration file.
78 utils.RemoveFile("/etc/xen/%s" % instance_name)
81 def _GetXMList(include_node):
82 """Return the list of running instances.
84 If the include_node argument is True, then we return information
85 for dom0 also, otherwise we filter that from the return value.
87 @return: list of (name, id, memory, vcpus, state, time spent)
90 for dummy in range(5):
91 result = utils.RunCmd(["xm", "list"])
94 logging.error("xm list failed (%s): %s", result.fail_reason,
99 raise errors.HypervisorError("xm list failed, retries"
100 " exceeded (%s): %s" %
101 (result.fail_reason, result.stderr))
103 # skip over the heading
104 lines = result.stdout.splitlines()[1:]
107 # The format of lines is:
108 # Name ID Mem(MiB) VCPUs State Time(s)
109 # Domain-0 0 3418 4 r----- 266.2
112 raise errors.HypervisorError("Can't parse output of xm list,"
115 data[1] = int(data[1])
116 data[2] = int(data[2])
117 data[3] = int(data[3])
118 data[5] = float(data[5])
119 except ValueError, err:
120 raise errors.HypervisorError("Can't parse output of xm list,"
121 " line: %s, error: %s" % (line, err))
123 # skip the Domain-0 (optional)
124 if include_node or data[0] != 'Domain-0':
129 def ListInstances(self):
130 """Get the list of running instances.
133 xm_list = self._GetXMList(False)
134 names = [info[0] for info in xm_list]
137 def GetInstanceInfo(self, instance_name):
138 """Get instance properties.
140 @param instance_name: the instance name
142 @return: tuple (name, id, memory, vcpus, stat, times)
145 xm_list = self._GetXMList(instance_name=="Domain-0")
148 if data[0] == instance_name:
153 def GetAllInstancesInfo(self):
154 """Get properties of all instances.
156 @return: list of tuples (name, id, memory, vcpus, stat, times)
159 xm_list = self._GetXMList(False)
162 def StartInstance(self, instance, block_devices, extra_args):
163 """Start an instance.
166 self._WriteConfigFile(instance, block_devices, extra_args)
167 result = utils.RunCmd(["xm", "create", instance.name])
170 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
171 (instance.name, result.fail_reason,
174 def StopInstance(self, instance, force=False):
178 self._RemoveConfigFile(instance.name)
180 command = ["xm", "destroy", instance.name]
182 command = ["xm", "shutdown", instance.name]
183 result = utils.RunCmd(command)
186 raise errors.HypervisorError("Failed to stop instance %s: %s" %
187 (instance.name, result.fail_reason))
189 def RebootInstance(self, instance):
190 """Reboot an instance.
193 result = utils.RunCmd(["xm", "reboot", instance.name])
196 raise errors.HypervisorError("Failed to reboot instance %s: %s" %
197 (instance.name, result.fail_reason))
199 def GetNodeInfo(self):
200 """Return information about the node.
202 @return: a dict with the following keys (memory values in MiB):
203 - memory_total: the total memory size on the node
204 - memory_free: the available memory on the node for instances
205 - memory_dom0: the memory used by the node itself, if available
206 - nr_cpus: total number of CPUs
207 - nr_nodes: in a NUMA system, the number of domains
208 - nr_sockets: the number of physical CPU sockets in the node
211 # note: in xen 3, memory has changed to total_memory
212 result = utils.RunCmd(["xm", "info"])
214 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
218 xmoutput = result.stdout.splitlines()
220 cores_per_socket = threads_per_core = nr_cpus = None
221 for line in xmoutput:
222 splitfields = line.split(":", 1)
224 if len(splitfields) > 1:
225 key = splitfields[0].strip()
226 val = splitfields[1].strip()
227 if key == 'memory' or key == 'total_memory':
228 result['memory_total'] = int(val)
229 elif key == 'free_memory':
230 result['memory_free'] = int(val)
231 elif key == 'nr_cpus':
232 nr_cpus = result['cpu_total'] = int(val)
233 elif key == 'nr_nodes':
234 result['cpu_nodes'] = int(val)
235 elif key == 'cores_per_socket':
236 cores_per_socket = int(val)
237 elif key == 'threads_per_core':
238 threads_per_core = int(val)
240 if (cores_per_socket is not None and
241 threads_per_core is not None and nr_cpus is not None):
242 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
244 dom0_info = self.GetInstanceInfo("Domain-0")
245 if dom0_info is not None:
246 result['memory_dom0'] = dom0_info[2]
251 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
252 """Return a command for connecting to the console of an instance.
255 return "xm console %s" % instance.name
259 """Verify the hypervisor.
261 For Xen, this verifies that the xend process is running.
264 result = utils.RunCmd(["xm", "info"])
266 return "'xm info' failed: %s" % result.fail_reason
269 def _GetConfigFileDiskData(disk_template, block_devices):
270 """Get disk directive for xen config file.
272 This method builds the xen config disk directive according to the
273 given disk_template and block_devices.
275 @param disk_template: string containing instance disk template
276 @param block_devices: list of tuples (cfdev, rldev):
277 - cfdev: dict containing ganeti config disk part
278 - rldev: ganeti.bdev.BlockDev object
280 @return: string containing disk directive for xen instance config file
284 constants.FD_LOOP: "file",
285 constants.FD_BLKTAP: "tap:aio",
288 if len(block_devices) > 24:
290 raise errors.HypervisorError("Too many disks")
291 # FIXME: instead of this hardcoding here, each of PVM/HVM should
292 # directly export their info (currently HVM will just sed this info)
293 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
294 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
295 if cfdev.mode == constants.DISK_RDWR:
299 if cfdev.dev_type == constants.LD_FILE:
300 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
301 dev_path, sd_name, mode)
303 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
304 disk_data.append(line)
308 def MigrationInfo(self, instance):
309 """Get instance information to perform a migration.
311 @type instance: L{objects.Instance}
312 @param instance: instance to be migrated
314 @return: content of the xen config file
317 return self._ReadConfigFile(instance.name)
319 def AcceptInstance(self, instance, info, target):
320 """Prepare to accept an instance.
322 @type instance: L{objects.Instance}
323 @param instance: instance to be accepted
325 @param info: content of the xen config file on the source node
327 @param target: target host (usually ip), on this node
332 def FinalizeMigration(self, instance, info, success):
333 """Finalize an instance migration.
335 After a successful migration we write the xen config file.
336 We do nothing on a failure, as we did not change anything at accept time.
338 @type instance: L{objects.Instance}
339 @param instance: instance whose migration is being aborted
341 @param info: content of the xen config file on the source node
342 @type success: boolean
343 @param success: whether the migration was a success or a failure
347 self._WriteConfigFileStatic(instance.name, info)
349 def MigrateInstance(self, instance, target, live):
350 """Migrate an instance to a target node.
352 The migration will not be attempted if the instance is not
355 @type instance: string
356 @param instance: instance name
358 @param target: ip address of the target node
360 @param live: perform a live migration
363 if self.GetInstanceInfo(instance) is None:
364 raise errors.HypervisorError("Instance not running, cannot migrate")
365 args = ["xm", "migrate"]
368 args.extend([instance, target])
369 result = utils.RunCmd(args)
371 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
372 (instance, result.output))
373 # remove old xen file after migration succeeded
375 self._RemoveConfigFile(instance)
376 except EnvironmentError:
377 logging.exception("Failure while removing instance config file")
380 class XenPvmHypervisor(XenHypervisor):
381 """Xen PVM hypervisor interface"""
384 constants.HV_KERNEL_PATH,
385 constants.HV_INITRD_PATH,
386 constants.HV_ROOT_PATH,
390 def CheckParameterSyntax(cls, hvparams):
391 """Check the given parameters for validity.
393 For the PVM hypervisor, this only check the existence of the
397 @param hvparams: dictionary with parameter names/value
398 @raise errors.HypervisorError: when a parameter is not valid
401 super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
403 if not hvparams[constants.HV_KERNEL_PATH]:
404 raise errors.HypervisorError("Need a kernel for the instance")
406 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
407 raise errors.HypervisorError("The kernel path must be an absolute path")
409 if not hvparams[constants.HV_ROOT_PATH]:
410 raise errors.HypervisorError("Need a root partition for the instance")
412 if hvparams[constants.HV_INITRD_PATH]:
413 if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
414 raise errors.HypervisorError("The initrd path must be an absolute path"
417 def ValidateParameters(self, hvparams):
418 """Check the given parameters for validity.
420 For the PVM hypervisor, this only check the existence of the
424 super(XenPvmHypervisor, self).ValidateParameters(hvparams)
426 kernel_path = hvparams[constants.HV_KERNEL_PATH]
427 if not os.path.isfile(kernel_path):
428 raise errors.HypervisorError("Instance kernel '%s' not found or"
429 " not a file" % kernel_path)
430 initrd_path = hvparams[constants.HV_INITRD_PATH]
431 if initrd_path and not os.path.isfile(initrd_path):
432 raise errors.HypervisorError("Instance initrd '%s' not found or"
433 " not a file" % initrd_path)
436 def _WriteConfigFile(cls, instance, block_devices, extra_args):
437 """Write the Xen config file for the instance.
441 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
444 kpath = instance.hvparams[constants.HV_KERNEL_PATH]
445 config.write("kernel = '%s'\n" % kpath)
448 initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
450 config.write("ramdisk = '%s'\n" % initrd_path)
452 # rest of the settings
453 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
454 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
455 config.write("name = '%s'\n" % instance.name)
458 for nic in instance.nics:
459 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
460 ip = getattr(nic, "ip", None)
462 nic_str += ", ip=%s" % ip
463 vif_data.append("'%s'" % nic_str)
465 config.write("vif = [%s]\n" % ",".join(vif_data))
466 config.write("disk = [%s]\n" % ",".join(
467 cls._GetConfigFileDiskData(instance.disk_template,
470 rpath = instance.hvparams[constants.HV_ROOT_PATH]
471 config.write("root = '%s ro'\n" % rpath)
472 config.write("on_poweroff = 'destroy'\n")
473 config.write("on_reboot = 'restart'\n")
474 config.write("on_crash = 'restart'\n")
476 config.write("extra = '%s'\n" % extra_args)
477 # just in case it exists
478 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
480 utils.WriteFile("/etc/xen/%s" % instance.name,
481 data=config.getvalue())
482 except EnvironmentError, err:
483 raise errors.HypervisorError("Cannot write Xen instance confile"
484 " file /etc/xen/%s: %s" %
485 (instance.name, err))
490 class XenHvmHypervisor(XenHypervisor):
491 """Xen HVM hypervisor interface"""
495 constants.HV_BOOT_ORDER,
496 constants.HV_CDROM_IMAGE_PATH,
497 constants.HV_DISK_TYPE,
498 constants.HV_NIC_TYPE,
500 constants.HV_VNC_BIND_ADDRESS,
504 def CheckParameterSyntax(cls, hvparams):
505 """Check the given parameter syntax.
508 super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
509 # boot order verification
510 boot_order = hvparams[constants.HV_BOOT_ORDER]
511 if not boot_order or len(boot_order.strip("acdn")) != 0:
512 raise errors.HypervisorError("Invalid boot order '%s' specified,"
513 " must be one or more of [acdn]" %
516 nic_type = hvparams[constants.HV_NIC_TYPE]
517 if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
518 raise errors.HypervisorError("Invalid NIC type %s specified for the Xen"
519 " HVM hypervisor. Please choose one of: %s"
521 constants.HT_HVM_VALID_NIC_TYPES))
522 disk_type = hvparams[constants.HV_DISK_TYPE]
523 if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
524 raise errors.HypervisorError("Invalid disk type %s specified for the Xen"
525 " HVM hypervisor. Please choose one of: %s"
527 constants.HT_HVM_VALID_DISK_TYPES))
528 # vnc_bind_address verification
529 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
531 if not utils.IsValidIP(vnc_bind_address):
532 raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
533 " like a valid IP address" %
536 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
537 if iso_path and not os.path.isabs(iso_path):
538 raise errors.HypervisorError("The path to the HVM CDROM image must"
539 " be an absolute path or None, not %s" %
542 def ValidateParameters(self, hvparams):
543 """Check the given parameters for validity.
545 For the PVM hypervisor, this only check the existence of the
549 @param hvparams: dictionary with parameter names/value
550 @raise errors.HypervisorError: when a parameter is not valid
553 super(XenHvmHypervisor, self).ValidateParameters(hvparams)
555 # hvm_cdrom_image_path verification
556 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
557 if iso_path and not os.path.isfile(iso_path):
558 raise errors.HypervisorError("The HVM CDROM image must either be a"
559 " regular file or a symlink pointing to"
560 " an existing regular file, not %s" %
564 def _WriteConfigFile(cls, instance, block_devices, extra_args):
565 """Create a Xen 3.1 HVM config file.
569 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
570 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
571 config.write("builder = 'hvm'\n")
572 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
573 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
574 config.write("name = '%s'\n" % instance.name)
575 if instance.hvparams[constants.HV_PAE]:
576 config.write("pae = 1\n")
578 config.write("pae = 0\n")
579 if instance.hvparams[constants.HV_ACPI]:
580 config.write("acpi = 1\n")
582 config.write("acpi = 0\n")
583 config.write("apic = 1\n")
586 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
588 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
589 config.write("boot = '%s'\n" % instance.hvparams[constants.HV_BOOT_ORDER])
590 config.write("sdl = 0\n")
591 config.write("usb = 1\n")
592 config.write("usbdevice = 'tablet'\n")
593 config.write("vnc = 1\n")
594 if instance.hvparams[constants.HV_VNC_BIND_ADDRESS] is None:
595 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
597 config.write("vnclisten = '%s'\n" %
598 instance.hvparams["vnc_bind_address"])
600 if instance.network_port > constants.VNC_BASE_PORT:
601 display = instance.network_port - constants.VNC_BASE_PORT
602 config.write("vncdisplay = %s\n" % display)
603 config.write("vncunused = 0\n")
605 config.write("# vncdisplay = 1\n")
606 config.write("vncunused = 1\n")
609 password = utils.ReadFile(constants.VNC_PASSWORD_FILE)
610 except EnvironmentError, err:
611 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
612 (constants.VNC_PASSWORD_FILE, err))
614 config.write("vncpasswd = '%s'\n" % password.rstrip())
616 config.write("serial = 'pty'\n")
617 config.write("localtime = 1\n")
620 nic_type = instance.hvparams[constants.HV_NIC_TYPE]
622 # ensure old instances don't change
623 nic_type_str = ", type=ioemu"
624 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
625 nic_type_str = ", type=paravirtualized"
627 nic_type_str = ", model=%s, type=ioemu" % nic_type
628 for nic in instance.nics:
629 nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
630 ip = getattr(nic, "ip", None)
632 nic_str += ", ip=%s" % ip
633 vif_data.append("'%s'" % nic_str)
635 config.write("vif = [%s]\n" % ",".join(vif_data))
636 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
638 disk_type = instance.hvparams[constants.HV_DISK_TYPE]
639 if disk_type in (None, constants.HT_DISK_IOEMU):
640 replacement = ",ioemu:hd"
643 disk_data = [line.replace(",sd", replacement) for line in disk_data]
644 iso_path = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
646 iso = "'file:%s,hdc:cdrom,r'" % iso_path
647 disk_data.append(iso)
649 config.write("disk = [%s]\n" % (",".join(disk_data)))
651 config.write("on_poweroff = 'destroy'\n")
652 config.write("on_reboot = 'restart'\n")
653 config.write("on_crash = 'restart'\n")
655 config.write("extra = '%s'\n" % extra_args)
656 # just in case it exists
657 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
659 utils.WriteFile("/etc/xen/%s" % instance.name,
660 data=config.getvalue())
661 except EnvironmentError, err:
662 raise errors.HypervisorError("Cannot write Xen instance confile"
663 " file /etc/xen/%s: %s" %
664 (instance.name, err))