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 '/etc/xen/xend-config.sxp',
50 '/etc/xen/scripts/vif-bridge',
54 def _WriteConfigFile(cls, instance, block_devices):
55 """Write the Xen config file for the instance.
58 raise NotImplementedError
61 def _WriteConfigFileStatic(instance_name, data):
62 """Write the Xen config file for the instance.
64 This version of the function just writes the config file from static data.
67 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
70 def _ReadConfigFile(instance_name):
71 """Returns the contents of the instance config file.
75 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
76 except EnvironmentError, err:
77 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
81 def _RemoveConfigFile(instance_name):
82 """Remove the xen configuration file.
85 utils.RemoveFile("/etc/xen/%s" % instance_name)
88 def _GetXMList(include_node):
89 """Return the list of running instances.
91 If the include_node argument is True, then we return information
92 for dom0 also, otherwise we filter that from the return value.
94 @return: list of (name, id, memory, vcpus, state, time spent)
98 result = utils.RunCmd(["xm", "list"])
101 logging.error("xm list failed (%s): %s", result.fail_reason,
106 raise errors.HypervisorError("xm list failed, retries"
107 " exceeded (%s): %s" %
108 (result.fail_reason, result.output))
110 # skip over the heading
111 lines = result.stdout.splitlines()[1:]
114 # The format of lines is:
115 # Name ID Mem(MiB) VCPUs State Time(s)
116 # Domain-0 0 3418 4 r----- 266.2
119 raise errors.HypervisorError("Can't parse output of xm list,"
122 data[1] = int(data[1])
123 data[2] = int(data[2])
124 data[3] = int(data[3])
125 data[5] = float(data[5])
126 except ValueError, err:
127 raise errors.HypervisorError("Can't parse output of xm list,"
128 " line: %s, error: %s" % (line, err))
130 # skip the Domain-0 (optional)
131 if include_node or data[0] != 'Domain-0':
136 def ListInstances(self):
137 """Get the list of running instances.
140 xm_list = self._GetXMList(False)
141 names = [info[0] for info in xm_list]
144 def GetInstanceInfo(self, instance_name):
145 """Get instance properties.
147 @param instance_name: the instance name
149 @return: tuple (name, id, memory, vcpus, stat, times)
152 xm_list = self._GetXMList(instance_name=="Domain-0")
155 if data[0] == instance_name:
160 def GetAllInstancesInfo(self):
161 """Get properties of all instances.
163 @return: list of tuples (name, id, memory, vcpus, stat, times)
166 xm_list = self._GetXMList(False)
169 def StartInstance(self, instance, block_devices):
170 """Start an instance.
173 self._WriteConfigFile(instance, block_devices)
174 result = utils.RunCmd(["xm", "create", instance.name])
177 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
178 (instance.name, result.fail_reason,
181 def StopInstance(self, instance, force=False, retry=False):
187 self._RemoveConfigFile(instance.name)
189 command = ["xm", "destroy", instance.name]
191 command = ["xm", "shutdown", instance.name]
192 result = utils.RunCmd(command)
195 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
196 (instance.name, result.fail_reason,
199 def RebootInstance(self, instance):
200 """Reboot an instance.
203 ini_info = self.GetInstanceInfo(instance.name)
204 result = utils.RunCmd(["xm", "reboot", instance.name])
207 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
208 (instance.name, result.fail_reason,
211 retries = self.REBOOT_RETRY_COUNT
213 new_info = self.GetInstanceInfo(instance.name)
214 # check if the domain ID has changed or the run time has
216 if new_info[1] != ini_info[1] or new_info[5] < ini_info[5]:
219 time.sleep(self.REBOOT_RETRY_INTERVAL)
223 raise errors.HypervisorError("Failed to reboot instance %s: instance"
224 " did not reboot in the expected interval" %
227 def GetNodeInfo(self):
228 """Return information about the node.
230 @return: a dict with the following keys (memory values in MiB):
231 - memory_total: the total memory size on the node
232 - memory_free: the available memory on the node for instances
233 - memory_dom0: the memory used by the node itself, if available
234 - nr_cpus: total number of CPUs
235 - nr_nodes: in a NUMA system, the number of domains
236 - nr_sockets: the number of physical CPU sockets in the node
239 # note: in xen 3, memory has changed to total_memory
240 result = utils.RunCmd(["xm", "info"])
242 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
246 xmoutput = result.stdout.splitlines()
248 cores_per_socket = threads_per_core = nr_cpus = None
249 for line in xmoutput:
250 splitfields = line.split(":", 1)
252 if len(splitfields) > 1:
253 key = splitfields[0].strip()
254 val = splitfields[1].strip()
255 if key == 'memory' or key == 'total_memory':
256 result['memory_total'] = int(val)
257 elif key == 'free_memory':
258 result['memory_free'] = int(val)
259 elif key == 'nr_cpus':
260 nr_cpus = result['cpu_total'] = int(val)
261 elif key == 'nr_nodes':
262 result['cpu_nodes'] = int(val)
263 elif key == 'cores_per_socket':
264 cores_per_socket = int(val)
265 elif key == 'threads_per_core':
266 threads_per_core = int(val)
268 if (cores_per_socket is not None and
269 threads_per_core is not None and nr_cpus is not None):
270 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
272 dom0_info = self.GetInstanceInfo("Domain-0")
273 if dom0_info is not None:
274 result['memory_dom0'] = dom0_info[2]
279 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
280 """Return a command for connecting to the console of an instance.
283 return "xm console %s" % instance.name
287 """Verify the hypervisor.
289 For Xen, this verifies that the xend process is running.
292 result = utils.RunCmd(["xm", "info"])
294 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
297 def _GetConfigFileDiskData(disk_template, block_devices):
298 """Get disk directive for xen config file.
300 This method builds the xen config disk directive according to the
301 given disk_template and block_devices.
303 @param disk_template: string containing instance disk template
304 @param block_devices: list of tuples (cfdev, rldev):
305 - cfdev: dict containing ganeti config disk part
306 - rldev: ganeti.bdev.BlockDev object
308 @return: string containing disk directive for xen instance config file
312 constants.FD_LOOP: "file",
313 constants.FD_BLKTAP: "tap:aio",
316 if len(block_devices) > 24:
318 raise errors.HypervisorError("Too many disks")
319 # FIXME: instead of this hardcoding here, each of PVM/HVM should
320 # directly export their info (currently HVM will just sed this info)
321 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
322 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
323 if cfdev.mode == constants.DISK_RDWR:
327 if cfdev.dev_type == constants.LD_FILE:
328 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
329 dev_path, sd_name, mode)
331 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
332 disk_data.append(line)
336 def MigrationInfo(self, instance):
337 """Get instance information to perform a migration.
339 @type instance: L{objects.Instance}
340 @param instance: instance to be migrated
342 @return: content of the xen config file
345 return self._ReadConfigFile(instance.name)
347 def AcceptInstance(self, instance, info, target):
348 """Prepare to accept an instance.
350 @type instance: L{objects.Instance}
351 @param instance: instance to be accepted
353 @param info: content of the xen config file on the source node
355 @param target: target host (usually ip), on this node
360 def FinalizeMigration(self, instance, info, success):
361 """Finalize an instance migration.
363 After a successful migration we write the xen config file.
364 We do nothing on a failure, as we did not change anything at accept time.
366 @type instance: L{objects.Instance}
367 @param instance: instance whose migration is being aborted
369 @param info: content of the xen config file on the source node
370 @type success: boolean
371 @param success: whether the migration was a success or a failure
375 self._WriteConfigFileStatic(instance.name, info)
377 def MigrateInstance(self, instance, target, live):
378 """Migrate an instance to a target node.
380 The migration will not be attempted if the instance is not
383 @type instance: string
384 @param instance: instance name
386 @param target: ip address of the target node
388 @param live: perform a live migration
391 if self.GetInstanceInfo(instance) is None:
392 raise errors.HypervisorError("Instance not running, cannot migrate")
393 args = ["xm", "migrate"]
396 args.extend([instance, target])
397 result = utils.RunCmd(args)
399 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
400 (instance, result.output))
401 # remove old xen file after migration succeeded
403 self._RemoveConfigFile(instance)
404 except EnvironmentError:
405 logging.exception("Failure while removing instance config file")
408 def PowercycleNode(cls):
409 """Xen-specific powercycle.
411 This first does a Linux reboot (which triggers automatically a Xen
412 reboot), and if that fails it tries to do a Xen reboot. The reason
413 we don't try a Xen reboot first is that the xen reboot launches an
414 external command which connects to the Xen hypervisor, and that
415 won't work in case the root filesystem is broken and/or the xend
416 daemon is not working.
420 cls.LinuxPowercycle()
422 utils.RunCmd(["xm", "debug", "R"])
425 class XenPvmHypervisor(XenHypervisor):
426 """Xen PVM hypervisor interface"""
429 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
430 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
431 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
432 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
433 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
434 constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK,
435 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
439 def _WriteConfigFile(cls, instance, block_devices):
440 """Write the Xen config file for the instance.
443 hvp = instance.hvparams
445 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
447 # if bootloader is True, use bootloader instead of kernel and ramdisk
449 if hvp[constants.HV_USE_BOOTLOADER]:
450 # bootloader handling
451 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
453 config.write("bootloader = '%s'\n" % bootloader_path)
455 raise errors.HypervisorError("Bootloader enabled, but missing"
458 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
460 config.write("bootargs = '%s'\n" % bootloader_args)
463 kpath = hvp[constants.HV_KERNEL_PATH]
464 config.write("kernel = '%s'\n" % kpath)
467 initrd_path = hvp[constants.HV_INITRD_PATH]
469 config.write("ramdisk = '%s'\n" % initrd_path)
471 # rest of the settings
472 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
473 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
474 config.write("name = '%s'\n" % instance.name)
477 for nic in instance.nics:
478 nic_str = "mac=%s" % (nic.mac)
479 ip = getattr(nic, "ip", None)
481 nic_str += ", ip=%s" % ip
482 vif_data.append("'%s'" % nic_str)
483 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
484 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
486 config.write("vif = [%s]\n" % ",".join(vif_data))
487 config.write("disk = [%s]\n" % ",".join(
488 cls._GetConfigFileDiskData(instance.disk_template,
491 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
492 config.write("on_poweroff = 'destroy'\n")
493 config.write("on_reboot = 'restart'\n")
494 config.write("on_crash = 'restart'\n")
495 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
496 # just in case it exists
497 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
499 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
500 except EnvironmentError, err:
501 raise errors.HypervisorError("Cannot write Xen instance confile"
502 " file /etc/xen/%s: %s" %
503 (instance.name, err))
508 class XenHvmHypervisor(XenHypervisor):
509 """Xen HVM hypervisor interface"""
511 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
512 constants.VNC_PASSWORD_FILE,
516 constants.HV_ACPI: hv_base.NO_CHECK,
517 constants.HV_BOOT_ORDER: (True, ) +
518 (lambda x: x and len(x.strip("acdn")) == 0,
519 "Invalid boot order specified, must be one or more of [acdn]",
521 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
522 constants.HV_DISK_TYPE:
523 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
524 constants.HV_NIC_TYPE:
525 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
526 constants.HV_PAE: hv_base.NO_CHECK,
527 constants.HV_VNC_BIND_ADDRESS:
528 (False, utils.IsValidIP,
529 "VNC bind address is not a valid IP address", None, None),
530 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
531 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
532 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
536 def _WriteConfigFile(cls, instance, block_devices):
537 """Create a Xen 3.1 HVM config file.
540 hvp = instance.hvparams
543 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
546 kpath = hvp[constants.HV_KERNEL_PATH]
547 config.write("kernel = '%s'\n" % kpath)
549 config.write("builder = 'hvm'\n")
550 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
551 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
552 config.write("name = '%s'\n" % instance.name)
553 if hvp[constants.HV_PAE]:
554 config.write("pae = 1\n")
556 config.write("pae = 0\n")
557 if hvp[constants.HV_ACPI]:
558 config.write("acpi = 1\n")
560 config.write("acpi = 0\n")
561 config.write("apic = 1\n")
562 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
563 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
564 config.write("sdl = 0\n")
565 config.write("usb = 1\n")
566 config.write("usbdevice = 'tablet'\n")
567 config.write("vnc = 1\n")
568 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
569 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
571 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
573 if instance.network_port > constants.VNC_BASE_PORT:
574 display = instance.network_port - constants.VNC_BASE_PORT
575 config.write("vncdisplay = %s\n" % display)
576 config.write("vncunused = 0\n")
578 config.write("# vncdisplay = 1\n")
579 config.write("vncunused = 1\n")
581 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
583 password = utils.ReadFile(vnc_pwd_file)
584 except EnvironmentError, err:
585 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
588 config.write("vncpasswd = '%s'\n" % password.rstrip())
590 config.write("serial = 'pty'\n")
591 config.write("localtime = 1\n")
594 nic_type = hvp[constants.HV_NIC_TYPE]
596 # ensure old instances don't change
597 nic_type_str = ", type=ioemu"
598 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
599 nic_type_str = ", type=paravirtualized"
601 nic_type_str = ", model=%s, type=ioemu" % nic_type
602 for nic in instance.nics:
603 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
604 ip = getattr(nic, "ip", None)
606 nic_str += ", ip=%s" % ip
607 vif_data.append("'%s'" % nic_str)
608 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
609 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
611 config.write("vif = [%s]\n" % ",".join(vif_data))
612 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
614 disk_type = hvp[constants.HV_DISK_TYPE]
615 if disk_type in (None, constants.HT_DISK_IOEMU):
616 replacement = ",ioemu:hd"
619 disk_data = [line.replace(",sd", replacement) for line in disk_data]
620 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
622 iso = "'file:%s,hdc:cdrom,r'" % iso_path
623 disk_data.append(iso)
625 config.write("disk = [%s]\n" % (",".join(disk_data)))
627 config.write("on_poweroff = 'destroy'\n")
628 config.write("on_reboot = 'restart'\n")
629 config.write("on_crash = 'restart'\n")
630 # just in case it exists
631 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
633 utils.WriteFile("/etc/xen/%s" % instance.name,
634 data=config.getvalue())
635 except EnvironmentError, err:
636 raise errors.HypervisorError("Cannot write Xen instance confile"
637 " file /etc/xen/%s: %s" %
638 (instance.name, err))