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):
185 self._RemoveConfigFile(instance.name)
187 command = ["xm", "destroy", instance.name]
189 command = ["xm", "shutdown", instance.name]
190 result = utils.RunCmd(command)
193 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
194 (instance.name, result.fail_reason,
197 def RebootInstance(self, instance):
198 """Reboot an instance.
201 ini_info = self.GetInstanceInfo(instance.name)
202 result = utils.RunCmd(["xm", "reboot", instance.name])
205 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
206 (instance.name, result.fail_reason,
209 retries = self.REBOOT_RETRY_COUNT
211 new_info = self.GetInstanceInfo(instance.name)
212 # check if the domain ID has changed or the run time has
214 if new_info[1] != ini_info[1] or new_info[5] < ini_info[5]:
217 time.sleep(self.REBOOT_RETRY_INTERVAL)
221 raise errors.HypervisorError("Failed to reboot instance %s: instance"
222 " did not reboot in the expected interval" %
225 def GetNodeInfo(self):
226 """Return information about the node.
228 @return: a dict with the following keys (memory values in MiB):
229 - memory_total: the total memory size on the node
230 - memory_free: the available memory on the node for instances
231 - memory_dom0: the memory used by the node itself, if available
232 - nr_cpus: total number of CPUs
233 - nr_nodes: in a NUMA system, the number of domains
234 - nr_sockets: the number of physical CPU sockets in the node
237 # note: in xen 3, memory has changed to total_memory
238 result = utils.RunCmd(["xm", "info"])
240 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
244 xmoutput = result.stdout.splitlines()
246 cores_per_socket = threads_per_core = nr_cpus = None
247 for line in xmoutput:
248 splitfields = line.split(":", 1)
250 if len(splitfields) > 1:
251 key = splitfields[0].strip()
252 val = splitfields[1].strip()
253 if key == 'memory' or key == 'total_memory':
254 result['memory_total'] = int(val)
255 elif key == 'free_memory':
256 result['memory_free'] = int(val)
257 elif key == 'nr_cpus':
258 nr_cpus = result['cpu_total'] = int(val)
259 elif key == 'nr_nodes':
260 result['cpu_nodes'] = int(val)
261 elif key == 'cores_per_socket':
262 cores_per_socket = int(val)
263 elif key == 'threads_per_core':
264 threads_per_core = int(val)
266 if (cores_per_socket is not None and
267 threads_per_core is not None and nr_cpus is not None):
268 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
270 dom0_info = self.GetInstanceInfo("Domain-0")
271 if dom0_info is not None:
272 result['memory_dom0'] = dom0_info[2]
277 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
278 """Return a command for connecting to the console of an instance.
281 return "xm console %s" % instance.name
285 """Verify the hypervisor.
287 For Xen, this verifies that the xend process is running.
290 result = utils.RunCmd(["xm", "info"])
292 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
295 def _GetConfigFileDiskData(disk_template, block_devices):
296 """Get disk directive for xen config file.
298 This method builds the xen config disk directive according to the
299 given disk_template and block_devices.
301 @param disk_template: string containing instance disk template
302 @param block_devices: list of tuples (cfdev, rldev):
303 - cfdev: dict containing ganeti config disk part
304 - rldev: ganeti.bdev.BlockDev object
306 @return: string containing disk directive for xen instance config file
310 constants.FD_LOOP: "file",
311 constants.FD_BLKTAP: "tap:aio",
314 if len(block_devices) > 24:
316 raise errors.HypervisorError("Too many disks")
317 # FIXME: instead of this hardcoding here, each of PVM/HVM should
318 # directly export their info (currently HVM will just sed this info)
319 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
320 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
321 if cfdev.mode == constants.DISK_RDWR:
325 if cfdev.dev_type == constants.LD_FILE:
326 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
327 dev_path, sd_name, mode)
329 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
330 disk_data.append(line)
334 def MigrationInfo(self, instance):
335 """Get instance information to perform a migration.
337 @type instance: L{objects.Instance}
338 @param instance: instance to be migrated
340 @return: content of the xen config file
343 return self._ReadConfigFile(instance.name)
345 def AcceptInstance(self, instance, info, target):
346 """Prepare to accept an instance.
348 @type instance: L{objects.Instance}
349 @param instance: instance to be accepted
351 @param info: content of the xen config file on the source node
353 @param target: target host (usually ip), on this node
358 def FinalizeMigration(self, instance, info, success):
359 """Finalize an instance migration.
361 After a successful migration we write the xen config file.
362 We do nothing on a failure, as we did not change anything at accept time.
364 @type instance: L{objects.Instance}
365 @param instance: instance whose migration is being aborted
367 @param info: content of the xen config file on the source node
368 @type success: boolean
369 @param success: whether the migration was a success or a failure
373 self._WriteConfigFileStatic(instance.name, info)
375 def MigrateInstance(self, instance, target, live):
376 """Migrate an instance to a target node.
378 The migration will not be attempted if the instance is not
381 @type instance: string
382 @param instance: instance name
384 @param target: ip address of the target node
386 @param live: perform a live migration
389 if self.GetInstanceInfo(instance) is None:
390 raise errors.HypervisorError("Instance not running, cannot migrate")
391 args = ["xm", "migrate"]
394 args.extend([instance, target])
395 result = utils.RunCmd(args)
397 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
398 (instance, result.output))
399 # remove old xen file after migration succeeded
401 self._RemoveConfigFile(instance)
402 except EnvironmentError:
403 logging.exception("Failure while removing instance config file")
406 def PowercycleNode(cls):
407 """Xen-specific powercycle.
409 This first does a Linux reboot (which triggers automatically a Xen
410 reboot), and if that fails it tries to do a Xen reboot. The reason
411 we don't try a Xen reboot first is that the xen reboot launches an
412 external command which connects to the Xen hypervisor, and that
413 won't work in case the root filesystem is broken and/or the xend
414 daemon is not working.
418 cls.LinuxPowercycle()
420 utils.RunCmd(["xm", "debug", "R"])
423 class XenPvmHypervisor(XenHypervisor):
424 """Xen PVM hypervisor interface"""
427 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
428 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
429 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
430 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
431 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
432 constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK,
433 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
437 def _WriteConfigFile(cls, instance, block_devices):
438 """Write the Xen config file for the instance.
441 hvp = instance.hvparams
443 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
445 # if bootloader is True, use bootloader instead of kernel and ramdisk
447 if hvp[constants.HV_USE_BOOTLOADER]:
448 # bootloader handling
449 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
451 config.write("bootloader = '%s'\n" % bootloader_path)
453 raise errors.HypervisorError("Bootloader enabled, but missing"
456 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
458 config.write("bootargs = '%s'\n" % bootloader_args)
461 kpath = hvp[constants.HV_KERNEL_PATH]
462 config.write("kernel = '%s'\n" % kpath)
465 initrd_path = hvp[constants.HV_INITRD_PATH]
467 config.write("ramdisk = '%s'\n" % initrd_path)
469 # rest of the settings
470 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
471 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
472 config.write("name = '%s'\n" % instance.name)
475 for nic in instance.nics:
476 nic_str = "mac=%s" % (nic.mac)
477 ip = getattr(nic, "ip", None)
479 nic_str += ", ip=%s" % ip
480 vif_data.append("'%s'" % nic_str)
481 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
482 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
484 config.write("vif = [%s]\n" % ",".join(vif_data))
485 config.write("disk = [%s]\n" % ",".join(
486 cls._GetConfigFileDiskData(instance.disk_template,
489 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
490 config.write("on_poweroff = 'destroy'\n")
491 config.write("on_reboot = 'restart'\n")
492 config.write("on_crash = 'restart'\n")
493 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
494 # just in case it exists
495 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
497 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
498 except EnvironmentError, err:
499 raise errors.HypervisorError("Cannot write Xen instance confile"
500 " file /etc/xen/%s: %s" %
501 (instance.name, err))
506 class XenHvmHypervisor(XenHypervisor):
507 """Xen HVM hypervisor interface"""
509 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
510 constants.VNC_PASSWORD_FILE,
514 constants.HV_ACPI: hv_base.NO_CHECK,
515 constants.HV_BOOT_ORDER: (True, ) +
516 (lambda x: x and len(x.strip("acdn")) == 0,
517 "Invalid boot order specified, must be one or more of [acdn]",
519 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
520 constants.HV_DISK_TYPE:
521 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
522 constants.HV_NIC_TYPE:
523 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
524 constants.HV_PAE: hv_base.NO_CHECK,
525 constants.HV_VNC_BIND_ADDRESS:
526 (False, utils.IsValidIP,
527 "VNC bind address is not a valid IP address", None, None),
528 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
529 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
530 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
534 def _WriteConfigFile(cls, instance, block_devices):
535 """Create a Xen 3.1 HVM config file.
538 hvp = instance.hvparams
541 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
544 kpath = hvp[constants.HV_KERNEL_PATH]
545 config.write("kernel = '%s'\n" % kpath)
547 config.write("builder = 'hvm'\n")
548 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
549 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
550 config.write("name = '%s'\n" % instance.name)
551 if hvp[constants.HV_PAE]:
552 config.write("pae = 1\n")
554 config.write("pae = 0\n")
555 if hvp[constants.HV_ACPI]:
556 config.write("acpi = 1\n")
558 config.write("acpi = 0\n")
559 config.write("apic = 1\n")
560 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
561 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
562 config.write("sdl = 0\n")
563 config.write("usb = 1\n")
564 config.write("usbdevice = 'tablet'\n")
565 config.write("vnc = 1\n")
566 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
567 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
569 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
571 if instance.network_port > constants.VNC_BASE_PORT:
572 display = instance.network_port - constants.VNC_BASE_PORT
573 config.write("vncdisplay = %s\n" % display)
574 config.write("vncunused = 0\n")
576 config.write("# vncdisplay = 1\n")
577 config.write("vncunused = 1\n")
579 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
581 password = utils.ReadFile(vnc_pwd_file)
582 except EnvironmentError, err:
583 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
586 config.write("vncpasswd = '%s'\n" % password.rstrip())
588 config.write("serial = 'pty'\n")
589 config.write("localtime = 1\n")
592 nic_type = hvp[constants.HV_NIC_TYPE]
594 # ensure old instances don't change
595 nic_type_str = ", type=ioemu"
596 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
597 nic_type_str = ", type=paravirtualized"
599 nic_type_str = ", model=%s, type=ioemu" % nic_type
600 for nic in instance.nics:
601 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
602 ip = getattr(nic, "ip", None)
604 nic_str += ", ip=%s" % ip
605 vif_data.append("'%s'" % nic_str)
606 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
607 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
609 config.write("vif = [%s]\n" % ",".join(vif_data))
610 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
612 disk_type = hvp[constants.HV_DISK_TYPE]
613 if disk_type in (None, constants.HT_DISK_IOEMU):
614 replacement = ",ioemu:hd"
617 disk_data = [line.replace(",sd", replacement) for line in disk_data]
618 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
620 iso = "'file:%s,hdc:cdrom,r'" % iso_path
621 disk_data.append(iso)
623 config.write("disk = [%s]\n" % (",".join(disk_data)))
625 config.write("on_poweroff = 'destroy'\n")
626 config.write("on_reboot = 'restart'\n")
627 config.write("on_crash = 'restart'\n")
628 # just in case it exists
629 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
631 utils.WriteFile("/etc/xen/%s" % instance.name,
632 data=config.getvalue())
633 except EnvironmentError, err:
634 raise errors.HypervisorError("Cannot write Xen instance confile"
635 " file /etc/xen/%s: %s" %
636 (instance.name, err))