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):
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)
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.
273 - instance: the name of the instance
274 - target: the ip of the target node
275 - live: whether to do live migration or not
277 Returns: none, errors will be signaled by exception.
279 The migration will not be attempted if the instance is not
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))
295 class XenPvmHypervisor(XenHypervisor):
296 """Xen PVM hypervisor interface"""
299 constants.HV_KERNEL_PATH,
300 constants.HV_INITRD_PATH,
304 def CheckParameterSyntax(cls, hvparams):
305 """Check the given parameters for validity.
307 For the PVM hypervisor, this only check the existence of the
311 @param hvparams: dictionary with parameter names/value
312 @raise errors.HypervisorError: when a parameter is not valid
315 super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
317 if not hvparams[constants.HV_KERNEL_PATH]:
318 raise errors.HypervisorError("Need a kernel for the instance")
320 if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
321 raise errors.HypervisorError("The kernel path must an absolute path")
323 if hvparams[constants.HV_INITRD_PATH]:
324 if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
325 raise errors.HypervisorError("The initrd path must an absolute path"
328 def ValidateParameters(self, hvparams):
329 """Check the given parameters for validity.
331 For the PVM hypervisor, this only check the existence of the
335 super(XenPvmHypervisor, self).ValidateParameters(hvparams)
337 kernel_path = hvparams[constants.HV_KERNEL_PATH]
338 if not os.path.isfile(kernel_path):
339 raise errors.HypervisorError("Instance kernel '%s' not found or"
340 " not a file" % kernel_path)
341 initrd_path = hvparams[constants.HV_INITRD_PATH]
342 if initrd_path and not os.path.isfile(initrd_path):
343 raise errors.HypervisorError("Instance initrd '%s' not found or"
344 " not a file" % initrd_path)
347 def _WriteConfigFile(cls, instance, block_devices, extra_args):
348 """Write the Xen config file for the instance.
352 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
355 kpath = instance.hvparams[constants.HV_KERNEL_PATH]
356 config.write("kernel = '%s'\n" % kpath)
359 initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
361 config.write("ramdisk = '%s'\n" % initrd_path)
363 # rest of the settings
364 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
365 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
366 config.write("name = '%s'\n" % instance.name)
369 for nic in instance.nics:
370 nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
371 ip = getattr(nic, "ip", None)
373 nic_str += ", ip=%s" % ip
374 vif_data.append("'%s'" % nic_str)
376 config.write("vif = [%s]\n" % ",".join(vif_data))
377 config.write("disk = [%s]\n" % ",".join(
378 cls._GetConfigFileDiskData(instance.disk_template,
380 config.write("root = '/dev/sda ro'\n")
381 config.write("on_poweroff = 'destroy'\n")
382 config.write("on_reboot = 'restart'\n")
383 config.write("on_crash = 'restart'\n")
385 config.write("extra = '%s'\n" % extra_args)
386 # just in case it exists
387 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
389 f = open("/etc/xen/%s" % instance.name, "w")
391 f.write(config.getvalue())
395 raise errors.OpExecError("Cannot write Xen instance confile"
396 " file /etc/xen/%s: %s" % (instance.name, err))
400 class XenHvmHypervisor(XenHypervisor):
401 """Xen HVM hypervisor interface"""
405 constants.HV_BOOT_ORDER,
406 constants.HV_CDROM_IMAGE_PATH,
407 constants.HV_DISK_TYPE,
408 constants.HV_NIC_TYPE,
410 constants.HV_VNC_BIND_ADDRESS,
414 def CheckParameterSyntax(cls, hvparams):
415 """Check the given parameter syntax.
418 super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
419 # boot order verification
420 boot_order = hvparams[constants.HV_BOOT_ORDER]
421 if len(boot_order.strip("acdn")) != 0:
422 raise errors.HypervisorError("Invalid boot order '%s' specified,"
423 " must be one or more of [acdn]" %
426 nic_type = hvparams[constants.HV_NIC_TYPE]
427 if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
428 raise errors.HypervisorError("Invalid NIC type %s specified for Xen HVM"
429 " hypervisor" % nic_type)
430 disk_type = hvparams[constants.HV_DISK_TYPE]
431 if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
432 raise errors.HypervisorError("Invalid disk type %s specified for Xen HVM"
433 " hypervisor" % disk_type)
434 # vnc_bind_address verification
435 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
436 if vnc_bind_address is not None:
437 if not utils.IsValidIP(vnc_bind_address):
438 raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
439 " like a valid IP address" %
442 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
443 if iso_path and not os.path.isabs(iso_path):
444 raise errors.HypervisorError("The path to the HVM CDROM image must"
445 " be an absolute path or None, not %s" %
448 def ValidateParameters(self, hvparams):
449 """Check the given parameters for validity.
451 For the PVM hypervisor, this only check the existence of the
455 @param hvparams: dictionary with parameter names/value
456 @raise errors.HypervisorError: when a parameter is not valid
459 super(XenHvmHypervisor, self).ValidateParameters(hvparams)
461 # hvm_cdrom_image_path verification
462 iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
463 if iso_path and not os.path.isfile(iso_path):
464 raise errors.HypervisorError("The HVM CDROM image must either be a"
465 " regular file or a symlink pointing to"
466 " an existing regular file, not %s" %
470 def _WriteConfigFile(cls, instance, block_devices, extra_args):
471 """Create a Xen 3.1 HVM config file.
475 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
476 config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
477 config.write("builder = 'hvm'\n")
478 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
479 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
480 config.write("name = '%s'\n" % instance.name)
481 if instance.hvparams[constants.HV_PAE]:
482 config.write("pae = 1\n")
484 config.write("pae = 0\n")
485 if instance.hvparams[constants.HV_ACPI]:
486 config.write("acpi = 1\n")
488 config.write("acpi = 0\n")
489 config.write("apic = 1\n")
492 config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
494 config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
495 if instance.hvparams[constants.HV_BOOT_ORDER] is None:
496 config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
498 config.write("boot = '%s'\n" % instance.hvparams["boot_order"])
499 config.write("sdl = 0\n")
500 config.write("usb = 1\n")
501 config.write("usbdevice = 'tablet'\n")
502 config.write("vnc = 1\n")
503 if instance.hvparams[constants.HV_VNC_BIND_ADDRESS] is None:
504 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
506 config.write("vnclisten = '%s'\n" %
507 instance.hvparams["vnc_bind_address"])
509 if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
510 display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
511 config.write("vncdisplay = %s\n" % display)
512 config.write("vncunused = 0\n")
514 config.write("# vncdisplay = 1\n")
515 config.write("vncunused = 1\n")
518 password_file = open(constants.VNC_PASSWORD_FILE, "r")
520 password = password_file.readline()
522 password_file.close()
524 raise errors.OpExecError("failed to open VNC password file %s " %
525 constants.VNC_PASSWORD_FILE)
527 config.write("vncpasswd = '%s'\n" % password.rstrip())
529 config.write("serial = 'pty'\n")
530 config.write("localtime = 1\n")
533 nic_type = instance.hvparams[constants.HV_NIC_TYPE]
535 # ensure old instances don't change
536 nic_type_str = ", type=ioemu"
537 elif nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
538 nic_type_str = ", type=paravirtualized"
540 nic_type_str = ", model=%s, type=ioemu" % nic_type
541 for nic in instance.nics:
542 nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
543 ip = getattr(nic, "ip", None)
545 nic_str += ", ip=%s" % ip
546 vif_data.append("'%s'" % nic_str)
548 config.write("vif = [%s]\n" % ",".join(vif_data))
549 disk_data = cls._GetConfigFileDiskData(instance.disk_template,
551 disk_type = instance.hvparams[constants.HV_DISK_TYPE]
552 if disk_type in (None, constants.HT_HVM_DEV_IOEMU):
553 replacement = ",ioemu:hd"
556 disk_data = [line.replace(",sd", replacement) for line in disk_data]
557 iso_path = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
559 iso = "'file:%s,hdc:cdrom,r'" % iso_path
560 disk_data.append(iso)
562 config.write("disk = [%s]\n" % (",".join(disk_data)))
564 config.write("on_poweroff = 'destroy'\n")
565 config.write("on_reboot = 'restart'\n")
566 config.write("on_crash = 'restart'\n")
568 config.write("extra = '%s'\n" % extra_args)
569 # just in case it exists
570 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
572 f = open("/etc/xen/%s" % instance.name, "w")
574 f.write(config.getvalue())
578 raise errors.OpExecError("Cannot write Xen instance confile"
579 " file /etc/xen/%s: %s" % (instance.name, err))