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
27 from cStringIO import StringIO
29 from ganeti import constants
30 from ganeti import errors
31 from ganeti import utils
32 from ganeti.hypervisor import hv_base
35 class XenHypervisor(hv_base.BaseHypervisor):
36 """Xen generic hypervisor interface
38 This is the Xen base class used for both Xen PVM and HVM. It contains
39 all the functionality that is identical for both.
43 REBOOT_RETRY_COUNT = 60
44 REBOOT_RETRY_INTERVAL = 10
47 '/etc/xen/xend-config.sxp',
48 '/etc/xen/scripts/vif-bridge',
52 def _WriteConfigFile(cls, instance, block_devices):
53 """Write the Xen config file for the instance.
56 raise NotImplementedError
59 def _WriteConfigFileStatic(instance_name, data):
60 """Write the Xen config file for the instance.
62 This version of the function just writes the config file from static data.
65 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
68 def _ReadConfigFile(instance_name):
69 """Returns the contents of the instance config file.
73 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
74 except EnvironmentError, err:
75 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
79 def _RemoveConfigFile(instance_name):
80 """Remove the xen configuration file.
83 utils.RemoveFile("/etc/xen/%s" % instance_name)
86 def _RunXmList(xmlist_errors):
87 """Helper function for L{_GetXMList} to run "xm list".
90 result = utils.RunCmd(["xm", "list"])
92 logging.error("xm list failed (%s): %s", result.fail_reason,
94 xmlist_errors.append(result)
95 raise utils.RetryAgain()
97 # skip over the heading
98 return result.stdout.splitlines()[1:]
101 def _GetXMList(cls, include_node):
102 """Return the list of running instances.
104 If the include_node argument is True, then we return information
105 for dom0 also, otherwise we filter that from the return value.
107 @return: list of (name, id, memory, vcpus, state, time spent)
112 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
113 except utils.RetryTimeout:
115 xmlist_result = xmlist_errors.pop()
117 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
118 (xmlist_result.fail_reason, xmlist_result.output))
120 errmsg = "xm list failed"
122 raise errors.HypervisorError(errmsg)
126 # The format of lines is:
127 # Name ID Mem(MiB) VCPUs State Time(s)
128 # Domain-0 0 3418 4 r----- 266.2
131 raise errors.HypervisorError("Can't parse output of xm list,"
134 data[1] = int(data[1])
135 data[2] = int(data[2])
136 data[3] = int(data[3])
137 data[5] = float(data[5])
138 except (TypeError, ValueError), err:
139 raise errors.HypervisorError("Can't parse output of xm list,"
140 " line: %s, error: %s" % (line, err))
142 # skip the Domain-0 (optional)
143 if include_node or data[0] != 'Domain-0':
148 def ListInstances(self):
149 """Get the list of running instances.
152 xm_list = self._GetXMList(False)
153 names = [info[0] for info in xm_list]
156 def GetInstanceInfo(self, instance_name):
157 """Get instance properties.
159 @param instance_name: the instance name
161 @return: tuple (name, id, memory, vcpus, stat, times)
164 xm_list = self._GetXMList(instance_name=="Domain-0")
167 if data[0] == instance_name:
172 def GetAllInstancesInfo(self):
173 """Get properties of all instances.
175 @return: list of tuples (name, id, memory, vcpus, stat, times)
178 xm_list = self._GetXMList(False)
181 def StartInstance(self, instance, block_devices):
182 """Start an instance.
185 self._WriteConfigFile(instance, block_devices)
186 result = utils.RunCmd(["xm", "create", instance.name])
189 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
190 (instance.name, result.fail_reason,
193 def StopInstance(self, instance, force=False, retry=False, name=None):
199 self._RemoveConfigFile(name)
201 command = ["xm", "destroy", name]
203 command = ["xm", "shutdown", name]
204 result = utils.RunCmd(command)
207 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
208 (name, result.fail_reason, result.output))
210 def RebootInstance(self, instance):
211 """Reboot an instance.
214 ini_info = self.GetInstanceInfo(instance.name)
217 raise errors.HypervisorError("Failed to reboot instance %s,"
218 " not running" % instance.name)
220 result = utils.RunCmd(["xm", "reboot", instance.name])
222 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
223 (instance.name, result.fail_reason,
226 def _CheckInstance():
227 new_info = self.GetInstanceInfo(instance.name)
229 # check if the domain ID has changed or the run time has decreased
230 if (new_info is not None and
231 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
234 raise utils.RetryAgain()
237 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
238 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
239 except utils.RetryTimeout:
240 raise errors.HypervisorError("Failed to reboot instance %s: instance"
241 " did not reboot in the expected interval" %
244 def GetNodeInfo(self):
245 """Return information about the node.
247 @return: a dict with the following keys (memory values in MiB):
248 - memory_total: the total memory size on the node
249 - memory_free: the available memory on the node for instances
250 - memory_dom0: the memory used by the node itself, if available
251 - nr_cpus: total number of CPUs
252 - nr_nodes: in a NUMA system, the number of domains
253 - nr_sockets: the number of physical CPU sockets in the node
256 # note: in xen 3, memory has changed to total_memory
257 result = utils.RunCmd(["xm", "info"])
259 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
263 xmoutput = result.stdout.splitlines()
265 cores_per_socket = threads_per_core = nr_cpus = None
266 for line in xmoutput:
267 splitfields = line.split(":", 1)
269 if len(splitfields) > 1:
270 key = splitfields[0].strip()
271 val = splitfields[1].strip()
272 if key == 'memory' or key == 'total_memory':
273 result['memory_total'] = int(val)
274 elif key == 'free_memory':
275 result['memory_free'] = int(val)
276 elif key == 'nr_cpus':
277 nr_cpus = result['cpu_total'] = int(val)
278 elif key == 'nr_nodes':
279 result['cpu_nodes'] = int(val)
280 elif key == 'cores_per_socket':
281 cores_per_socket = int(val)
282 elif key == 'threads_per_core':
283 threads_per_core = int(val)
285 if (cores_per_socket is not None and
286 threads_per_core is not None and nr_cpus is not None):
287 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
289 dom0_info = self.GetInstanceInfo("Domain-0")
290 if dom0_info is not None:
291 result['memory_dom0'] = dom0_info[2]
296 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
297 """Return a command for connecting to the console of an instance.
300 return "xm console %s" % instance.name
304 """Verify the hypervisor.
306 For Xen, this verifies that the xend process is running.
309 result = utils.RunCmd(["xm", "info"])
311 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
314 def _GetConfigFileDiskData(block_devices):
315 """Get disk directive for xen config file.
317 This method builds the xen config disk directive according to the
318 given disk_template and block_devices.
320 @param block_devices: list of tuples (cfdev, rldev):
321 - cfdev: dict containing ganeti config disk part
322 - rldev: ganeti.bdev.BlockDev object
324 @return: string containing disk directive for xen instance config file
328 constants.FD_LOOP: "file",
329 constants.FD_BLKTAP: "tap:aio",
332 if len(block_devices) > 24:
334 raise errors.HypervisorError("Too many disks")
335 # FIXME: instead of this hardcoding here, each of PVM/HVM should
336 # directly export their info (currently HVM will just sed this info)
337 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
338 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
339 if cfdev.mode == constants.DISK_RDWR:
343 if cfdev.dev_type == constants.LD_FILE:
344 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
345 dev_path, sd_name, mode)
347 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
348 disk_data.append(line)
352 def MigrationInfo(self, instance):
353 """Get instance information to perform a migration.
355 @type instance: L{objects.Instance}
356 @param instance: instance to be migrated
358 @return: content of the xen config file
361 return self._ReadConfigFile(instance.name)
363 def AcceptInstance(self, instance, info, target):
364 """Prepare to accept an instance.
366 @type instance: L{objects.Instance}
367 @param instance: instance to be accepted
369 @param info: content of the xen config file on the source node
371 @param target: target host (usually ip), on this node
376 def FinalizeMigration(self, instance, info, success):
377 """Finalize an instance migration.
379 After a successful migration we write the xen config file.
380 We do nothing on a failure, as we did not change anything at accept time.
382 @type instance: L{objects.Instance}
383 @param instance: instance whose migration is being finalized
385 @param info: content of the xen config file on the source node
386 @type success: boolean
387 @param success: whether the migration was a success or a failure
391 self._WriteConfigFileStatic(instance.name, info)
393 def MigrateInstance(self, instance, target, live):
394 """Migrate an instance to a target node.
396 The migration will not be attempted if the instance is not
399 @type instance: L{objects.Instance}
400 @param instance: the instance to be migrated
402 @param target: ip address of the target node
404 @param live: perform a live migration
407 if self.GetInstanceInfo(instance.name) is None:
408 raise errors.HypervisorError("Instance not running, cannot migrate")
410 port = instance.hvparams[constants.HV_MIGRATION_PORT]
412 if not utils.TcpPing(target, port, live_port_needed=True):
413 raise errors.HypervisorError("Remote host %s not listening on port"
414 " %s, cannot migrate" % (target, port))
416 args = ["xm", "migrate", "-p", "%d" % port]
419 args.extend([instance.name, target])
420 result = utils.RunCmd(args)
422 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
423 (instance.name, result.output))
424 # remove old xen file after migration succeeded
426 self._RemoveConfigFile(instance.name)
427 except EnvironmentError:
428 logging.exception("Failure while removing instance config file")
431 def PowercycleNode(cls):
432 """Xen-specific powercycle.
434 This first does a Linux reboot (which triggers automatically a Xen
435 reboot), and if that fails it tries to do a Xen reboot. The reason
436 we don't try a Xen reboot first is that the xen reboot launches an
437 external command which connects to the Xen hypervisor, and that
438 won't work in case the root filesystem is broken and/or the xend
439 daemon is not working.
443 cls.LinuxPowercycle()
445 utils.RunCmd(["xm", "debug", "R"])
448 class XenPvmHypervisor(XenHypervisor):
449 """Xen PVM hypervisor interface"""
452 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
453 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
454 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
455 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
456 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
457 constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK,
458 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
459 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
463 def _WriteConfigFile(cls, instance, block_devices):
464 """Write the Xen config file for the instance.
467 hvp = instance.hvparams
469 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
471 # if bootloader is True, use bootloader instead of kernel and ramdisk
473 if hvp[constants.HV_USE_BOOTLOADER]:
474 # bootloader handling
475 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
477 config.write("bootloader = '%s'\n" % bootloader_path)
479 raise errors.HypervisorError("Bootloader enabled, but missing"
482 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
484 config.write("bootargs = '%s'\n" % bootloader_args)
487 kpath = hvp[constants.HV_KERNEL_PATH]
488 config.write("kernel = '%s'\n" % kpath)
491 initrd_path = hvp[constants.HV_INITRD_PATH]
493 config.write("ramdisk = '%s'\n" % initrd_path)
495 # rest of the settings
496 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
497 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
498 config.write("name = '%s'\n" % instance.name)
501 for nic in instance.nics:
502 nic_str = "mac=%s" % (nic.mac)
503 ip = getattr(nic, "ip", None)
505 nic_str += ", ip=%s" % ip
506 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
507 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
508 vif_data.append("'%s'" % nic_str)
510 disk_data = cls._GetConfigFileDiskData(block_devices)
512 config.write("vif = [%s]\n" % ",".join(vif_data))
513 config.write("disk = [%s]\n" % ",".join(disk_data))
515 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
516 config.write("on_poweroff = 'destroy'\n")
517 config.write("on_reboot = 'restart'\n")
518 config.write("on_crash = 'restart'\n")
519 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
520 # just in case it exists
521 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
523 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
524 except EnvironmentError, err:
525 raise errors.HypervisorError("Cannot write Xen instance confile"
526 " file /etc/xen/%s: %s" %
527 (instance.name, err))
532 class XenHvmHypervisor(XenHypervisor):
533 """Xen HVM hypervisor interface"""
535 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
536 constants.VNC_PASSWORD_FILE,
540 constants.HV_ACPI: hv_base.NO_CHECK,
541 constants.HV_BOOT_ORDER: (True, ) +
542 (lambda x: x and len(x.strip("acdn")) == 0,
543 "Invalid boot order specified, must be one or more of [acdn]",
545 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
546 constants.HV_DISK_TYPE:
547 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
548 constants.HV_NIC_TYPE:
549 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
550 constants.HV_PAE: hv_base.NO_CHECK,
551 constants.HV_VNC_BIND_ADDRESS:
552 (False, utils.IsValidIP,
553 "VNC bind address is not a valid IP address", None, None),
554 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
555 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
556 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
557 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
558 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
562 def _WriteConfigFile(cls, instance, block_devices):
563 """Create a Xen 3.1 HVM config file.
566 hvp = instance.hvparams
569 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
572 kpath = hvp[constants.HV_KERNEL_PATH]
573 config.write("kernel = '%s'\n" % kpath)
575 config.write("builder = 'hvm'\n")
576 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
577 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
578 config.write("name = '%s'\n" % instance.name)
579 if hvp[constants.HV_PAE]:
580 config.write("pae = 1\n")
582 config.write("pae = 0\n")
583 if hvp[constants.HV_ACPI]:
584 config.write("acpi = 1\n")
586 config.write("acpi = 0\n")
587 config.write("apic = 1\n")
588 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
589 config.write("boot = '%s'\n" % hvp[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 hvp[constants.HV_VNC_BIND_ADDRESS] is None:
595 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
597 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
599 if instance.network_port > constants.VNC_BASE_PORT:
600 display = instance.network_port - constants.VNC_BASE_PORT
601 config.write("vncdisplay = %s\n" % display)
602 config.write("vncunused = 0\n")
604 config.write("# vncdisplay = 1\n")
605 config.write("vncunused = 1\n")
607 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
609 password = utils.ReadFile(vnc_pwd_file)
610 except EnvironmentError, err:
611 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
614 config.write("vncpasswd = '%s'\n" % password.rstrip())
616 config.write("serial = 'pty'\n")
617 if hvp[constants.HV_USE_LOCALTIME]:
618 config.write("localtime = 1\n")
621 nic_type = hvp[constants.HV_NIC_TYPE]
623 # ensure old instances don't change
624 nic_type_str = ", type=ioemu"
625 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
626 nic_type_str = ", type=paravirtualized"
628 nic_type_str = ", model=%s, type=ioemu" % nic_type
629 for nic in instance.nics:
630 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
631 ip = getattr(nic, "ip", None)
633 nic_str += ", ip=%s" % ip
634 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
635 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
636 vif_data.append("'%s'" % nic_str)
638 config.write("vif = [%s]\n" % ",".join(vif_data))
639 disk_data = cls._GetConfigFileDiskData(block_devices)
640 disk_type = hvp[constants.HV_DISK_TYPE]
641 if disk_type in (None, constants.HT_DISK_IOEMU):
642 replacement = ",ioemu:hd"
645 disk_data = [line.replace(",sd", replacement) for line in disk_data]
646 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
648 iso = "'file:%s,hdc:cdrom,r'" % iso_path
649 disk_data.append(iso)
651 config.write("disk = [%s]\n" % (",".join(disk_data)))
653 config.write("on_poweroff = 'destroy'\n")
654 config.write("on_reboot = 'restart'\n")
655 config.write("on_crash = 'restart'\n")
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))