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 _RemoveConfigFile(instance_name):
55 """Remove the xen configuration file.
58 utils.RemoveFile("/etc/xen/%s" % instance_name)
61 def _GetXMList(include_node):
62 """Return the list of running instances.
64 If the include_node argument is True, then we return information
65 for dom0 also, otherwise we filter that from the return value.
67 @return: list of (name, id, memory, vcpus, state, time spent)
70 for dummy in range(5):
71 result = utils.RunCmd(["xm", "list"])
74 logging.error("xm list failed (%s): %s", result.fail_reason,
79 raise errors.HypervisorError("xm list failed, retries"
80 " exceeded (%s): %s" %
81 (result.fail_reason, result.stderr))
83 # skip over the heading
84 lines = result.stdout.splitlines()[1:]
87 # The format of lines is:
88 # Name ID Mem(MiB) VCPUs State Time(s)
89 # Domain-0 0 3418 4 r----- 266.2
92 raise errors.HypervisorError("Can't parse output of xm list,"
95 data[1] = int(data[1])
96 data[2] = int(data[2])
97 data[3] = int(data[3])
98 data[5] = float(data[5])
99 except ValueError, err:
100 raise errors.HypervisorError("Can't parse output of xm list,"
101 " line: %s, error: %s" % (line, err))
103 # skip the Domain-0 (optional)
104 if include_node or data[0] != 'Domain-0':
109 def ListInstances(self):
110 """Get the list of running instances.
113 xm_list = self._GetXMList(False)
114 names = [info[0] for info in xm_list]
117 def GetInstanceInfo(self, instance_name):
118 """Get instance properties.
120 @param instance_name: the instance name
122 @return: tuple (name, id, memory, vcpus, stat, times)
125 xm_list = self._GetXMList(instance_name=="Domain-0")
128 if data[0] == instance_name:
133 def GetAllInstancesInfo(self):
134 """Get properties of all instances.
136 @return: list of tuples (name, id, memory, vcpus, stat, times)
139 xm_list = self._GetXMList(False)
142 def StartInstance(self, instance, block_devices, extra_args):
143 """Start an instance.
146 self._WriteConfigFile(instance, block_devices, extra_args)
147 result = utils.RunCmd(["xm", "create", instance.name])
150 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
151 (instance.name, result.fail_reason,
154 def StopInstance(self, instance, force=False):
158 self._RemoveConfigFile(instance.name)
160 command = ["xm", "destroy", instance.name]
162 command = ["xm", "shutdown", instance.name]
163 result = utils.RunCmd(command)
166 raise errors.HypervisorError("Failed to stop instance %s: %s" %
167 (instance.name, result.fail_reason))
169 def RebootInstance(self, instance):
170 """Reboot an instance.
173 result = utils.RunCmd(["xm", "reboot", instance.name])
176 raise errors.HypervisorError("Failed to reboot instance %s: %s" %
177 (instance.name, result.fail_reason))
179 def GetNodeInfo(self):
180 """Return information about the node.
182 @return: a dict with the following keys (values in MiB):
183 - memory_total: the total memory size on the node
184 - memory_free: the available memory on the node for instances
185 - memory_dom0: the memory used by the node itself, if available
188 # note: in xen 3, memory has changed to total_memory
189 result = utils.RunCmd(["xm", "info"])
191 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
195 xmoutput = result.stdout.splitlines()
197 for line in xmoutput:
198 splitfields = line.split(":", 1)
200 if len(splitfields) > 1:
201 key = splitfields[0].strip()
202 val = splitfields[1].strip()
203 if key == 'memory' or key == 'total_memory':
204 result['memory_total'] = int(val)
205 elif key == 'free_memory':
206 result['memory_free'] = int(val)
207 elif key == 'nr_cpus':
208 result['cpu_total'] = int(val)
209 dom0_info = self.GetInstanceInfo("Domain-0")
210 if dom0_info is not None:
211 result['memory_dom0'] = dom0_info[2]
216 def GetShellCommandForConsole(instance):
217 """Return a command for connecting to the console of an instance.
220 return "xm console %s" % instance.name
224 """Verify the hypervisor.
226 For Xen, this verifies that the xend process is running.
229 result = utils.RunCmd(["xm", "info"])
231 return "'xm info' failed: %s" % result.fail_reason
234 def _GetConfigFileDiskData(disk_template, block_devices):
235 """Get disk directive for xen config file.
237 This method builds the xen config disk directive according to the
238 given disk_template and block_devices.
240 @param disk_template: string containing instance disk template
241 @param block_devices: list of tuples (cfdev, rldev):
242 - cfdev: dict containing ganeti config disk part
243 - rldev: ganeti.bdev.BlockDev object
245 @return: string containing disk directive for xen instance config file
249 constants.FD_LOOP: "file",
250 constants.FD_BLKTAP: "tap:aio",
253 if len(block_devices) > 24:
255 raise errors.HypervisorError("Too many disks")
256 # FIXME: instead of this hardcoding here, each of PVM/HVM should
257 # directly export their info (currently HVM will just sed this info)
258 namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
259 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
260 if cfdev.dev_type == constants.LD_FILE:
261 line = "'%s:%s,%s,w'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
264 line = "'phy:%s,%s,w'" % (dev_path, sd_name)
265 disk_data.append(line)
269 def MigrateInstance(self, instance, target, live):
270 """Migrate an instance to a target node.
272 The migration will not be attempted if the instance is not
275 @type instance: string
276 @param instance: instance name
278 @param target: ip address of the target node
280 @param live: perform a live migration
283 if self.GetInstanceInfo(instance) is None:
284 raise errors.HypervisorError("Instance not running, cannot migrate")
285 args = ["xm", "migrate"]
288 args.extend([instance, target])
289 result = utils.RunCmd(args)
291 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
292 (instance, result.output))
293 # remove old xen file after migration succeeded
295 self._RemoveConfigFile(instance)
296 except EnvironmentError:
297 logging.exception("Failure while removing instance config file")
300 class XenPvmHypervisor(XenHypervisor):
301 """Xen PVM hypervisor interface"""
304 constants.HV_KERNEL_PATH,
305 constants.HV_INITRD_PATH,
309 def CheckParameterSyntax(cls, hvparams):
310 """Check the given parameters for validity.
312 For the PVM hypervisor, this only check the existence of the
316 @param hvparams: dictionary with parameter names/value
317 @raise errors.HypervisorError: when a parameter is not valid
320 super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
322 if not hvparams[constants.HV_KERNEL_PATH]:
323 raise errors.HypervisorError("Need a kernel for the instance")
325 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
326 raise errors.HypervisorError("The kernel path must an absolute path")
328 if hvparams[constants.HV_INITRD_PATH]:
329 if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
330 raise errors.HypervisorError("The initrd path must an absolute path"
333 def ValidateParameters(self, hvparams):
334 """Check the given parameters for validity.
336 For the PVM hypervisor, this only check the existence of the
340 super(XenPvmHypervisor, self).ValidateParameters(hvparams)
342 kernel_path = hvparams[constants.HV_KERNEL_PATH]
343 if not os.path.isfile(kernel_path):
344 raise errors.HypervisorError("Instance kernel '%s' not found or"
345 " not a file" % kernel_path)
346 initrd_path = hvparams[constants.HV_INITRD_PATH]
347 if initrd_path and not os.path.isfile(initrd_path):
348 raise errors.HypervisorError("Instance initrd '%s' not found or"
349 " not a file" % initrd_path)
352 def _WriteConfigFile(cls, instance, block_devices, extra_args):
353 """Write the Xen config file for the instance.
357 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
360 kpath = instance.hvparams[constants.HV_KERNEL_PATH]
361 config.write("kernel = '%s'\n" % kpath)
364 initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
366 config.write("ramdisk = '%s'\n" % initrd_path)
368 # rest of the settings
369 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
370 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
371 config.write("name = '%s'\n" % instance.name)
374 for nic in instance.nics:
375 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
376 ip = getattr(nic, "ip", None)
378 nic_str += ", ip=%s" % ip
379 vif_data.append("'%s'" % nic_str)
381 config.write("vif = [%s]\n" % ",".join(vif_data))
382 config.write("disk = [%s]\n" % ",".join(
383 cls._GetConfigFileDiskData(instance.disk_template,
385 config.write("root = '/dev/sda ro'\n")
386 config.write("on_poweroff = 'destroy'\n")
387 config.write("on_reboot = 'restart'\n")
388 config.write("on_crash = 'restart'\n")
390 config.write("extra = '%s'\n" % extra_args)
391 # just in case it exists
392 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
394 f = open("/etc/xen/%s" % instance.name, "w")
396 f.write(config.getvalue())
400 raise errors.OpExecError("Cannot write Xen instance confile"
401 " file /etc/xen/%s: %s" % (instance.name, err))
405 class XenHvmHypervisor(XenHypervisor):
406 """Xen HVM hypervisor interface"""
410 constants.HV_BOOT_ORDER,
411 constants.HV_CDROM_IMAGE_PATH,
412 constants.HV_DISK_TYPE,
413 constants.HV_NIC_TYPE,
415 constants.HV_VNC_BIND_ADDRESS,
419 def CheckParameterSyntax(cls, hvparams):
420 """Check the given parameter syntax.
423 super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
424 # boot order verification
425 boot_order = hvparams[constants.HV_BOOT_ORDER]
426 if len(boot_order.strip("acdn")) != 0:
427 raise errors.HypervisorError("Invalid boot order '%s' specified,"
428 " must be one or more of [acdn]" %
431 nic_type = hvparams[constants.HV_NIC_TYPE]
432 if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
433 raise errors.HypervisorError("Invalid NIC type %s specified for Xen HVM"
434 " hypervisor" % nic_type)
435 disk_type = hvparams[constants.HV_DISK_TYPE]
436 if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
437 raise errors.HypervisorError("Invalid disk type %s specified for Xen HVM"
438 " hypervisor" % disk_type)
439 # vnc_bind_address verification
440 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
441 if vnc_bind_address is not None:
442 if not utils.IsValidIP(vnc_bind_address):
443 raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
444 " like a valid IP address" %
447 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
448 if iso_path and not os.path.isabs(iso_path):
449 raise errors.HypervisorError("The path to the HVM CDROM image must"
450 " be an absolute path or None, not %s" %
453 def ValidateParameters(self, hvparams):
454 """Check the given parameters for validity.
456 For the PVM hypervisor, this only check the existence of the
460 @param hvparams: dictionary with parameter names/value
461 @raise errors.HypervisorError: when a parameter is not valid
464 super(XenHvmHypervisor, self).ValidateParameters(hvparams)
466 # hvm_cdrom_image_path verification
467 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
468 if iso_path and not os.path.isfile(iso_path):
469 raise errors.HypervisorError("The HVM CDROM image must either be a"
470 " regular file or a symlink pointing to"
471 " an existing regular file, not %s" %
475 def _WriteConfigFile(cls, instance, block_devices, extra_args):
476 """Create a Xen 3.1 HVM config file.
480 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
481 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
482 config.write("builder = 'hvm'\n")
483 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
484 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
485 config.write("name = '%s'\n" % instance.name)
486 if instance.hvparams[constants.HV_PAE]:
487 config.write("pae = 1\n")
489 config.write("pae = 0\n")
490 if instance.hvparams[constants.HV_ACPI]:
491 config.write("acpi = 1\n")
493 config.write("acpi = 0\n")
494 config.write("apic = 1\n")
497 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
499 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
500 if instance.hvparams[constants.HV_BOOT_ORDER] is None:
501 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
503 config.write("boot = '%s'\n" % instance.hvparams["boot_order"])
504 config.write("sdl = 0\n")
505 config.write("usb = 1\n")
506 config.write("usbdevice = 'tablet'\n")
507 config.write("vnc = 1\n")
508 if instance.hvparams[constants.HV_VNC_BIND_ADDRESS] is None:
509 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
511 config.write("vnclisten = '%s'\n" %
512 instance.hvparams["vnc_bind_address"])
514 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
515 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
516 config.write("vncdisplay = %s\n" % display)
517 config.write("vncunused = 0\n")
519 config.write("# vncdisplay = 1\n")
520 config.write("vncunused = 1\n")
523 password_file = open(constants.VNC_PASSWORD_FILE, "r")
525 password = password_file.readline()
527 password_file.close()
529 raise errors.OpExecError("failed to open VNC password file %s " %
530 constants.VNC_PASSWORD_FILE)
532 config.write("vncpasswd = '%s'\n" % password.rstrip())
534 config.write("serial = 'pty'\n")
535 config.write("localtime = 1\n")
538 nic_type = instance.hvparams[constants.HV_NIC_TYPE]
540 # ensure old instances don't change
541 nic_type_str = ", type=ioemu"
542 elif nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
543 nic_type_str = ", type=paravirtualized"
545 nic_type_str = ", model=%s, type=ioemu" % nic_type
546 for nic in instance.nics:
547 nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
548 ip = getattr(nic, "ip", None)
550 nic_str += ", ip=%s" % ip
551 vif_data.append("'%s'" % nic_str)
553 config.write("vif = [%s]\n" % ",".join(vif_data))
554 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
556 disk_type = instance.hvparams[constants.HV_DISK_TYPE]
557 if disk_type in (None, constants.HT_HVM_DEV_IOEMU):
558 replacement = ",ioemu:hd"
561 disk_data = [line.replace(",sd", replacement) for line in disk_data]
562 iso_path = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
564 iso = "'file:%s,hdc:cdrom,r'" % iso_path
565 disk_data.append(iso)
567 config.write("disk = [%s]\n" % (",".join(disk_data)))
569 config.write("on_poweroff = 'destroy'\n")
570 config.write("on_reboot = 'restart'\n")
571 config.write("on_crash = 'restart'\n")
573 config.write("extra = '%s'\n" % extra_args)
574 # just in case it exists
575 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
577 f = open("/etc/xen/%s" % instance.name, "w")
579 f.write(config.getvalue())
583 raise errors.OpExecError("Cannot write Xen instance confile"
584 " file /etc/xen/%s: %s" % (instance.name, err))