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, err:
297 logger.Error("Failure while removing instance config file: %s" %
301 class XenPvmHypervisor(XenHypervisor):
302 """Xen PVM hypervisor interface"""
305 constants.HV_KERNEL_PATH,
306 constants.HV_INITRD_PATH,
310 def CheckParameterSyntax(cls, hvparams):
311 """Check the given parameters for validity.
313 For the PVM hypervisor, this only check the existence of the
317 @param hvparams: dictionary with parameter names/value
318 @raise errors.HypervisorError: when a parameter is not valid
321 super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
323 if not hvparams[constants.HV_KERNEL_PATH]:
324 raise errors.HypervisorError("Need a kernel for the instance")
326 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
327 raise errors.HypervisorError("The kernel path must an absolute path")
329 if hvparams[constants.HV_INITRD_PATH]:
330 if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
331 raise errors.HypervisorError("The initrd path must an absolute path"
334 def ValidateParameters(self, hvparams):
335 """Check the given parameters for validity.
337 For the PVM hypervisor, this only check the existence of the
341 super(XenPvmHypervisor, self).ValidateParameters(hvparams)
343 kernel_path = hvparams[constants.HV_KERNEL_PATH]
344 if not os.path.isfile(kernel_path):
345 raise errors.HypervisorError("Instance kernel '%s' not found or"
346 " not a file" % kernel_path)
347 initrd_path = hvparams[constants.HV_INITRD_PATH]
348 if initrd_path and not os.path.isfile(initrd_path):
349 raise errors.HypervisorError("Instance initrd '%s' not found or"
350 " not a file" % initrd_path)
353 def _WriteConfigFile(cls, instance, block_devices, extra_args):
354 """Write the Xen config file for the instance.
358 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
361 kpath = instance.hvparams[constants.HV_KERNEL_PATH]
362 config.write("kernel = '%s'\n" % kpath)
365 initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
367 config.write("ramdisk = '%s'\n" % initrd_path)
369 # rest of the settings
370 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
371 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
372 config.write("name = '%s'\n" % instance.name)
375 for nic in instance.nics:
376 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
377 ip = getattr(nic, "ip", None)
379 nic_str += ", ip=%s" % ip
380 vif_data.append("'%s'" % nic_str)
382 config.write("vif = [%s]\n" % ",".join(vif_data))
383 config.write("disk = [%s]\n" % ",".join(
384 cls._GetConfigFileDiskData(instance.disk_template,
386 config.write("root = '/dev/sda ro'\n")
387 config.write("on_poweroff = 'destroy'\n")
388 config.write("on_reboot = 'restart'\n")
389 config.write("on_crash = 'restart'\n")
391 config.write("extra = '%s'\n" % extra_args)
392 # just in case it exists
393 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
395 f = open("/etc/xen/%s" % instance.name, "w")
397 f.write(config.getvalue())
401 raise errors.OpExecError("Cannot write Xen instance confile"
402 " file /etc/xen/%s: %s" % (instance.name, err))
406 class XenHvmHypervisor(XenHypervisor):
407 """Xen HVM hypervisor interface"""
411 constants.HV_BOOT_ORDER,
412 constants.HV_CDROM_IMAGE_PATH,
413 constants.HV_DISK_TYPE,
414 constants.HV_NIC_TYPE,
416 constants.HV_VNC_BIND_ADDRESS,
420 def CheckParameterSyntax(cls, hvparams):
421 """Check the given parameter syntax.
424 super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
425 # boot order verification
426 boot_order = hvparams[constants.HV_BOOT_ORDER]
427 if len(boot_order.strip("acdn")) != 0:
428 raise errors.HypervisorError("Invalid boot order '%s' specified,"
429 " must be one or more of [acdn]" %
432 nic_type = hvparams[constants.HV_NIC_TYPE]
433 if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
434 raise errors.HypervisorError("Invalid NIC type %s specified for Xen HVM"
435 " hypervisor" % nic_type)
436 disk_type = hvparams[constants.HV_DISK_TYPE]
437 if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
438 raise errors.HypervisorError("Invalid disk type %s specified for Xen HVM"
439 " hypervisor" % disk_type)
440 # vnc_bind_address verification
441 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
442 if vnc_bind_address is not None:
443 if not utils.IsValidIP(vnc_bind_address):
444 raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
445 " like a valid IP address" %
448 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
449 if iso_path and not os.path.isabs(iso_path):
450 raise errors.HypervisorError("The path to the HVM CDROM image must"
451 " be an absolute path or None, not %s" %
454 def ValidateParameters(self, hvparams):
455 """Check the given parameters for validity.
457 For the PVM hypervisor, this only check the existence of the
461 @param hvparams: dictionary with parameter names/value
462 @raise errors.HypervisorError: when a parameter is not valid
465 super(XenHvmHypervisor, self).ValidateParameters(hvparams)
467 # hvm_cdrom_image_path verification
468 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
469 if iso_path and not os.path.isfile(iso_path):
470 raise errors.HypervisorError("The HVM CDROM image must either be a"
471 " regular file or a symlink pointing to"
472 " an existing regular file, not %s" %
476 def _WriteConfigFile(cls, instance, block_devices, extra_args):
477 """Create a Xen 3.1 HVM config file.
481 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
482 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
483 config.write("builder = 'hvm'\n")
484 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
485 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
486 config.write("name = '%s'\n" % instance.name)
487 if instance.hvparams[constants.HV_PAE]:
488 config.write("pae = 1\n")
490 config.write("pae = 0\n")
491 if instance.hvparams[constants.HV_ACPI]:
492 config.write("acpi = 1\n")
494 config.write("acpi = 0\n")
495 config.write("apic = 1\n")
498 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
500 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
501 if instance.hvparams[constants.HV_BOOT_ORDER] is None:
502 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
504 config.write("boot = '%s'\n" % instance.hvparams["boot_order"])
505 config.write("sdl = 0\n")
506 config.write("usb = 1\n")
507 config.write("usbdevice = 'tablet'\n")
508 config.write("vnc = 1\n")
509 if instance.hvparams[constants.HV_VNC_BIND_ADDRESS] is None:
510 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
512 config.write("vnclisten = '%s'\n" %
513 instance.hvparams["vnc_bind_address"])
515 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
516 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
517 config.write("vncdisplay = %s\n" % display)
518 config.write("vncunused = 0\n")
520 config.write("# vncdisplay = 1\n")
521 config.write("vncunused = 1\n")
524 password_file = open(constants.VNC_PASSWORD_FILE, "r")
526 password = password_file.readline()
528 password_file.close()
530 raise errors.OpExecError("failed to open VNC password file %s " %
531 constants.VNC_PASSWORD_FILE)
533 config.write("vncpasswd = '%s'\n" % password.rstrip())
535 config.write("serial = 'pty'\n")
536 config.write("localtime = 1\n")
539 nic_type = instance.hvparams[constants.HV_NIC_TYPE]
541 # ensure old instances don't change
542 nic_type_str = ", type=ioemu"
543 elif nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
544 nic_type_str = ", type=paravirtualized"
546 nic_type_str = ", model=%s, type=ioemu" % nic_type
547 for nic in instance.nics:
548 nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
549 ip = getattr(nic, "ip", None)
551 nic_str += ", ip=%s" % ip
552 vif_data.append("'%s'" % nic_str)
554 config.write("vif = [%s]\n" % ",".join(vif_data))
555 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
557 disk_type = instance.hvparams[constants.HV_DISK_TYPE]
558 if disk_type in (None, constants.HT_HVM_DEV_IOEMU):
559 replacement = ",ioemu:hd"
562 disk_data = [line.replace(",sd", replacement) for line in disk_data]
563 iso_path = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
565 iso = "'file:%s,hdc:cdrom,r'" % iso_path
566 disk_data.append(iso)
568 config.write("disk = [%s]\n" % (",".join(disk_data)))
570 config.write("on_poweroff = 'destroy'\n")
571 config.write("on_reboot = 'restart'\n")
572 config.write("on_crash = 'restart'\n")
574 config.write("extra = '%s'\n" % extra_args)
575 # just in case it exists
576 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
578 f = open("/etc/xen/%s" % instance.name, "w")
580 f.write(config.getvalue())
584 raise errors.OpExecError("Cannot write Xen instance confile"
585 " file /etc/xen/%s: %s" % (instance.name, err))