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 (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
208 # note: in xen 3, memory has changed to total_memory
209 result = utils.RunCmd(["xm", "info"])
211 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
215 xmoutput = result.stdout.splitlines()
217 for line in xmoutput:
218 splitfields = line.split(":", 1)
220 if len(splitfields) > 1:
221 key = splitfields[0].strip()
222 val = splitfields[1].strip()
223 if key == 'memory' or key == 'total_memory':
224 result['memory_total'] = int(val)
225 elif key == 'free_memory':
226 result['memory_free'] = int(val)
227 elif key == 'nr_cpus':
228 result['cpu_total'] = int(val)
229 dom0_info = self.GetInstanceInfo("Domain-0")
230 if dom0_info is not None:
231 result['memory_dom0'] = dom0_info[2]
236 def GetShellCommandForConsole(cls, instance):
237 """Return a command for connecting to the console of an instance.
240 return "xm console %s" % instance.name
244 """Verify the hypervisor.
246 For Xen, this verifies that the xend process is running.
249 result = utils.RunCmd(["xm", "info"])
251 return "'xm info' failed: %s" % result.fail_reason
254 def _GetConfigFileDiskData(disk_template, block_devices):
255 """Get disk directive for xen config file.
257 This method builds the xen config disk directive according to the
258 given disk_template and block_devices.
260 @param disk_template: string containing instance disk template
261 @param block_devices: list of tuples (cfdev, rldev):
262 - cfdev: dict containing ganeti config disk part
263 - rldev: ganeti.bdev.BlockDev object
265 @return: string containing disk directive for xen instance config file
269 constants.FD_LOOP: "file",
270 constants.FD_BLKTAP: "tap:aio",
273 if len(block_devices) > 24:
275 raise errors.HypervisorError("Too many disks")
276 # FIXME: instead of this hardcoding here, each of PVM/HVM should
277 # directly export their info (currently HVM will just sed this info)
278 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
279 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
280 if cfdev.mode == constants.DISK_RDWR:
284 if cfdev.dev_type == constants.LD_FILE:
285 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
286 dev_path, sd_name, mode)
288 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
289 disk_data.append(line)
293 def MigrationInfo(self, instance):
294 """Get instance information to perform a migration.
296 @type instance: L{objects.Instance}
297 @param instance: instance to be migrated
299 @return: content of the xen config file
302 return self._ReadConfigFile(instance.name)
304 def AcceptInstance(self, instance, info, target):
305 """Prepare to accept an instance.
307 @type instance: L{objects.Instance}
308 @param instance: instance to be accepted
310 @param info: content of the xen config file on the source node
312 @param target: target host (usually ip), on this node
317 def FinalizeMigration(self, instance, info, success):
318 """Finalize an instance migration.
320 After a successful migration we write the xen config file.
321 We do nothing on a failure, as we did not change anything at accept time.
323 @type instance: L{objects.Instance}
324 @param instance: instance whose migration is being aborted
326 @param info: content of the xen config file on the source node
327 @type success: boolean
328 @param success: whether the migration was a success or a failure
332 self._WriteConfigFileStatic(instance.name, info)
334 def MigrateInstance(self, instance, target, live):
335 """Migrate an instance to a target node.
337 The migration will not be attempted if the instance is not
340 @type instance: string
341 @param instance: instance name
343 @param target: ip address of the target node
345 @param live: perform a live migration
348 if self.GetInstanceInfo(instance) is None:
349 raise errors.HypervisorError("Instance not running, cannot migrate")
350 args = ["xm", "migrate"]
353 args.extend([instance, target])
354 result = utils.RunCmd(args)
356 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
357 (instance, result.output))
358 # remove old xen file after migration succeeded
360 self._RemoveConfigFile(instance)
361 except EnvironmentError:
362 logging.exception("Failure while removing instance config file")
365 class XenPvmHypervisor(XenHypervisor):
366 """Xen PVM hypervisor interface"""
369 constants.HV_KERNEL_PATH,
370 constants.HV_INITRD_PATH,
371 constants.HV_ROOT_PATH,
375 def CheckParameterSyntax(cls, hvparams):
376 """Check the given parameters for validity.
378 For the PVM hypervisor, this only check the existence of the
382 @param hvparams: dictionary with parameter names/value
383 @raise errors.HypervisorError: when a parameter is not valid
386 super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
388 if not hvparams[constants.HV_KERNEL_PATH]:
389 raise errors.HypervisorError("Need a kernel for the instance")
391 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
392 raise errors.HypervisorError("The kernel path must be an absolute path")
394 if not hvparams[constants.HV_ROOT_PATH]:
395 raise errors.HypervisorError("Need a root partition for the instance")
397 if hvparams[constants.HV_INITRD_PATH]:
398 if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
399 raise errors.HypervisorError("The initrd path must be an absolute path"
402 def ValidateParameters(self, hvparams):
403 """Check the given parameters for validity.
405 For the PVM hypervisor, this only check the existence of the
409 super(XenPvmHypervisor, self).ValidateParameters(hvparams)
411 kernel_path = hvparams[constants.HV_KERNEL_PATH]
412 if not os.path.isfile(kernel_path):
413 raise errors.HypervisorError("Instance kernel '%s' not found or"
414 " not a file" % kernel_path)
415 initrd_path = hvparams[constants.HV_INITRD_PATH]
416 if initrd_path and not os.path.isfile(initrd_path):
417 raise errors.HypervisorError("Instance initrd '%s' not found or"
418 " not a file" % initrd_path)
421 def _WriteConfigFile(cls, instance, block_devices, extra_args):
422 """Write the Xen config file for the instance.
426 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
429 kpath = instance.hvparams[constants.HV_KERNEL_PATH]
430 config.write("kernel = '%s'\n" % kpath)
433 initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
435 config.write("ramdisk = '%s'\n" % initrd_path)
437 # rest of the settings
438 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
439 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
440 config.write("name = '%s'\n" % instance.name)
443 for nic in instance.nics:
444 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
445 ip = getattr(nic, "ip", None)
447 nic_str += ", ip=%s" % ip
448 vif_data.append("'%s'" % nic_str)
450 config.write("vif = [%s]\n" % ",".join(vif_data))
451 config.write("disk = [%s]\n" % ",".join(
452 cls._GetConfigFileDiskData(instance.disk_template,
455 rpath = instance.hvparams[constants.HV_ROOT_PATH]
456 config.write("root = '%s ro'\n" % rpath)
457 config.write("on_poweroff = 'destroy'\n")
458 config.write("on_reboot = 'restart'\n")
459 config.write("on_crash = 'restart'\n")
461 config.write("extra = '%s'\n" % extra_args)
462 # just in case it exists
463 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
465 utils.WriteFile("/etc/xen/%s" % instance.name,
466 data=config.getvalue())
467 except EnvironmentError, err:
468 raise errors.HypervisorError("Cannot write Xen instance confile"
469 " file /etc/xen/%s: %s" %
470 (instance.name, err))
475 class XenHvmHypervisor(XenHypervisor):
476 """Xen HVM hypervisor interface"""
480 constants.HV_BOOT_ORDER,
481 constants.HV_CDROM_IMAGE_PATH,
482 constants.HV_DISK_TYPE,
483 constants.HV_NIC_TYPE,
485 constants.HV_VNC_BIND_ADDRESS,
489 def CheckParameterSyntax(cls, hvparams):
490 """Check the given parameter syntax.
493 super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
494 # boot order verification
495 boot_order = hvparams[constants.HV_BOOT_ORDER]
496 if len(boot_order.strip("acdn")) != 0:
497 raise errors.HypervisorError("Invalid boot order '%s' specified,"
498 " must be one or more of [acdn]" %
501 nic_type = hvparams[constants.HV_NIC_TYPE]
502 if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
503 raise errors.HypervisorError("Invalid NIC type %s specified for Xen HVM"
504 " hypervisor" % nic_type)
505 disk_type = hvparams[constants.HV_DISK_TYPE]
506 if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
507 raise errors.HypervisorError("Invalid disk type %s specified for Xen HVM"
508 " hypervisor" % disk_type)
509 # vnc_bind_address verification
510 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
511 if vnc_bind_address is not None:
512 if not utils.IsValidIP(vnc_bind_address):
513 raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
514 " like a valid IP address" %
517 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
518 if iso_path and not os.path.isabs(iso_path):
519 raise errors.HypervisorError("The path to the HVM CDROM image must"
520 " be an absolute path or None, not %s" %
523 def ValidateParameters(self, hvparams):
524 """Check the given parameters for validity.
526 For the PVM hypervisor, this only check the existence of the
530 @param hvparams: dictionary with parameter names/value
531 @raise errors.HypervisorError: when a parameter is not valid
534 super(XenHvmHypervisor, self).ValidateParameters(hvparams)
536 # hvm_cdrom_image_path verification
537 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
538 if iso_path and not os.path.isfile(iso_path):
539 raise errors.HypervisorError("The HVM CDROM image must either be a"
540 " regular file or a symlink pointing to"
541 " an existing regular file, not %s" %
545 def _WriteConfigFile(cls, instance, block_devices, extra_args):
546 """Create a Xen 3.1 HVM config file.
550 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
551 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
552 config.write("builder = 'hvm'\n")
553 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
554 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
555 config.write("name = '%s'\n" % instance.name)
556 if instance.hvparams[constants.HV_PAE]:
557 config.write("pae = 1\n")
559 config.write("pae = 0\n")
560 if instance.hvparams[constants.HV_ACPI]:
561 config.write("acpi = 1\n")
563 config.write("acpi = 0\n")
564 config.write("apic = 1\n")
567 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
569 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
570 if instance.hvparams[constants.HV_BOOT_ORDER] is None:
571 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
573 config.write("boot = '%s'\n" % instance.hvparams["boot_order"])
574 config.write("sdl = 0\n")
575 config.write("usb = 1\n")
576 config.write("usbdevice = 'tablet'\n")
577 config.write("vnc = 1\n")
578 if instance.hvparams[constants.HV_VNC_BIND_ADDRESS] is None:
579 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
581 config.write("vnclisten = '%s'\n" %
582 instance.hvparams["vnc_bind_address"])
584 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
585 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
586 config.write("vncdisplay = %s\n" % display)
587 config.write("vncunused = 0\n")
589 config.write("# vncdisplay = 1\n")
590 config.write("vncunused = 1\n")
593 password = utils.ReadFile(constants.VNC_PASSWORD_FILE)
594 except EnvironmentError, err:
595 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
596 (constants.VNC_PASSWORD_FILE, err))
598 config.write("vncpasswd = '%s'\n" % password.rstrip())
600 config.write("serial = 'pty'\n")
601 config.write("localtime = 1\n")
604 nic_type = instance.hvparams[constants.HV_NIC_TYPE]
606 # ensure old instances don't change
607 nic_type_str = ", type=ioemu"
608 elif nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
609 nic_type_str = ", type=paravirtualized"
611 nic_type_str = ", model=%s, type=ioemu" % nic_type
612 for nic in instance.nics:
613 nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
614 ip = getattr(nic, "ip", None)
616 nic_str += ", ip=%s" % ip
617 vif_data.append("'%s'" % nic_str)
619 config.write("vif = [%s]\n" % ",".join(vif_data))
620 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
622 disk_type = instance.hvparams[constants.HV_DISK_TYPE]
623 if disk_type in (None, constants.HT_HVM_DEV_IOEMU):
624 replacement = ",ioemu:hd"
627 disk_data = [line.replace(",sd", replacement) for line in disk_data]
628 iso_path = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
630 iso = "'file:%s,hdc:cdrom,r'" % iso_path
631 disk_data.append(iso)
633 config.write("disk = [%s]\n" % (",".join(disk_data)))
635 config.write("on_poweroff = 'destroy'\n")
636 config.write("on_reboot = 'restart'\n")
637 config.write("on_crash = 'restart'\n")
639 config.write("extra = '%s'\n" % extra_args)
640 # just in case it exists
641 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
643 utils.WriteFile("/etc/xen/%s" % instance.name,
644 data=config.getvalue())
645 except EnvironmentError, err:
646 raise errors.HypervisorError("Cannot write Xen instance confile"
647 " file /etc/xen/%s: %s" %
648 (instance.name, err))