4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
28 import string # pylint: disable=W0402
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
36 from ganeti import netutils
37 from ganeti import objects
38 from ganeti import pathutils
39 from ganeti import ssconf
42 XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
43 XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
44 VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
46 _DOM0_NAME = "Domain-0"
47 _DISK_LETTERS = string.ascii_lowercase
50 constants.FD_LOOP: "file",
51 constants.FD_BLKTAP: "tap:aio",
52 constants.FD_BLKTAP2: "tap2:tapdisk:aio",
56 def _CreateConfigCpus(cpu_mask):
57 """Create a CPU config string for Xen's config file.
60 # Convert the string CPU mask to a list of list of int's
61 cpu_list = utils.ParseMultiCpuMask(cpu_mask)
63 if len(cpu_list) == 1:
64 all_cpu_mapping = cpu_list[0]
65 if all_cpu_mapping == constants.CPU_PINNING_OFF:
66 # If CPU pinning has 1 entry that's "all", then remove the
67 # parameter from the config file
70 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
71 # VM) to one physical CPU, using format 'cpu = "C"'
72 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
76 if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
77 cpu_map = constants.CPU_PINNING_ALL_XEN
79 cpu_map = ",".join(map(str, vcpu))
80 return "\"%s\"" % cpu_map
82 # build the result string in format 'cpus = [ "c", "c", "c" ]',
83 # where each c is a physical CPU number, a range, a list, or any
85 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
88 def _RunXmList(fn, xmllist_errors):
89 """Helper function for L{_GetXmList} to run "xm list".
92 @param fn: Function returning result of running C{xm list}
93 @type xmllist_errors: list
94 @param xmllist_errors: Error list
100 logging.error("xm list failed (%s): %s", result.fail_reason,
102 xmllist_errors.append(result)
103 raise utils.RetryAgain()
105 # skip over the heading
106 return result.stdout.splitlines()
109 def _ParseXmList(lines, include_node):
110 """Parses the output of C{xm list}.
113 @param lines: Output lines of C{xm list}
114 @type include_node: boolean
115 @param include_node: If True, return information for Dom0
116 @return: list of tuple containing (name, id, memory, vcpus, state, time
122 # Iterate through all lines while ignoring header
123 for line in lines[1:]:
124 # The format of lines is:
125 # Name ID Mem(MiB) VCPUs State Time(s)
126 # Domain-0 0 3418 4 r----- 266.2
129 raise errors.HypervisorError("Can't parse output of xm list,"
132 data[1] = int(data[1])
133 data[2] = int(data[2])
134 data[3] = int(data[3])
135 data[5] = float(data[5])
136 except (TypeError, ValueError), err:
137 raise errors.HypervisorError("Can't parse output of xm list,"
138 " line: %s, error: %s" % (line, err))
140 # skip the Domain-0 (optional)
141 if include_node or data[0] != _DOM0_NAME:
147 def _GetXmList(fn, include_node, _timeout=5):
148 """Return the list of running instances.
150 See L{_RunXmList} and L{_ParseXmList} for parameter details.
155 lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout,
156 args=(fn, xmllist_errors))
157 except utils.RetryTimeout:
159 xmlist_result = xmllist_errors.pop()
161 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
162 (xmlist_result.fail_reason, xmlist_result.output))
164 errmsg = "xm list failed"
166 raise errors.HypervisorError(errmsg)
168 return _ParseXmList(lines, include_node)
171 def _IsInstanceRunning(instance_info):
172 return instance_info == "r-----" \
173 or instance_info == "-b----"
176 def _IsInstanceShutdown(instance_info):
177 return instance_info == "---s--"
180 def _ParseNodeInfo(info):
181 """Return information about the node.
183 @return: a dict with the following keys (memory values in MiB):
184 - memory_total: the total memory size on the node
185 - memory_free: the available memory on the node for instances
186 - nr_cpus: total number of CPUs
187 - nr_nodes: in a NUMA system, the number of domains
188 - nr_sockets: the number of physical CPU sockets in the node
189 - hv_version: the hypervisor version in the form (major, minor)
193 cores_per_socket = threads_per_core = nr_cpus = None
194 xen_major, xen_minor = None, None
198 for line in info.splitlines():
199 fields = line.split(":", 1)
204 (key, val) = map(lambda s: s.strip(), fields)
206 # Note: in Xen 3, memory has changed to total_memory
207 if key in ("memory", "total_memory"):
208 memory_total = int(val)
209 elif key == "free_memory":
210 memory_free = int(val)
211 elif key == "nr_cpus":
212 nr_cpus = result["cpu_total"] = int(val)
213 elif key == "nr_nodes":
214 result["cpu_nodes"] = int(val)
215 elif key == "cores_per_socket":
216 cores_per_socket = int(val)
217 elif key == "threads_per_core":
218 threads_per_core = int(val)
219 elif key == "xen_major":
221 elif key == "xen_minor":
224 if None not in [cores_per_socket, threads_per_core, nr_cpus]:
225 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
227 if memory_free is not None:
228 result["memory_free"] = memory_free
230 if memory_total is not None:
231 result["memory_total"] = memory_total
233 if not (xen_major is None or xen_minor is None):
234 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
239 def _MergeInstanceInfo(info, fn):
240 """Updates node information from L{_ParseNodeInfo} with instance info.
243 @param info: Result from L{_ParseNodeInfo}
245 @param fn: Function returning result of running C{xm list}
251 for (name, _, mem, vcpus, _, _) in fn(True):
252 if name == _DOM0_NAME:
253 info["memory_dom0"] = mem
254 info["dom0_cpus"] = vcpus
256 # Include Dom0 in total memory usage
259 memory_free = info.get("memory_free")
260 memory_total = info.get("memory_total")
262 # Calculate memory used by hypervisor
263 if None not in [memory_total, memory_free, total_instmem]:
264 info["memory_hv"] = memory_total - memory_free - total_instmem
269 def _GetNodeInfo(info, fn):
270 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
273 return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
276 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
277 _letters=_DISK_LETTERS):
278 """Get disk directives for Xen config file.
280 This method builds the xen config disk directive according to the
281 given disk_template and block_devices.
283 @param block_devices: list of tuples (cfdev, rldev):
284 - cfdev: dict containing ganeti config disk part
285 - rldev: ganeti.bdev.BlockDev object
286 @param blockdev_prefix: a string containing blockdevice prefix,
287 e.g. "sd" for /dev/sda
289 @return: string containing disk directive for xen instance config file
292 if len(block_devices) > len(_letters):
293 raise errors.HypervisorError("Too many disks")
297 for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
298 sd_name = blockdev_prefix + sd_suffix
300 if cfdev.mode == constants.DISK_RDWR:
305 if cfdev.dev_type == constants.LD_FILE:
306 driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
310 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
315 class XenHypervisor(hv_base.BaseHypervisor):
316 """Xen generic hypervisor interface
318 This is the Xen base class used for both Xen PVM and HVM. It contains
319 all the functionality that is identical for both.
323 REBOOT_RETRY_COUNT = 60
324 REBOOT_RETRY_INTERVAL = 10
325 _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
326 _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
327 _DIRS = [_ROOT_DIR, _NICS_DIR]
334 ANCILLARY_FILES_OPT = [
338 def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
339 hv_base.BaseHypervisor.__init__(self)
342 self._cfgdir = pathutils.XEN_CONFIG_DIR
344 self._cfgdir = _cfgdir
346 if _run_cmd_fn is None:
347 self._run_cmd_fn = utils.RunCmd
349 self._run_cmd_fn = _run_cmd_fn
353 def _GetCommand(self):
354 """Returns Xen command to use.
357 if self._cmd is None:
358 # TODO: Make command a hypervisor parameter
359 cmd = constants.XEN_CMD
363 if cmd not in constants.KNOWN_XEN_COMMANDS:
364 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
368 def _RunXen(self, args, timeout=None):
369 """Wrapper around L{utils.process.RunCmd} to run Xen command.
371 If a timeout (in seconds) is specified, the command will be terminated after
372 that number of seconds.
374 @see: L{utils.process.RunCmd}
379 if timeout is not None:
380 cmd.extend(["timeout", str(timeout)])
382 cmd.extend([self._GetCommand()])
385 return self._run_cmd_fn(cmd)
387 def _ConfigFileName(self, instance_name):
388 """Get the config file name for an instance.
390 @param instance_name: instance name
391 @type instance_name: str
392 @return: fully qualified path to instance config file
396 return utils.PathJoin(self._cfgdir, instance_name)
399 def _WriteNICInfoFile(cls, instance, idx, nic):
400 """Write the Xen config file for the instance.
402 This version of the function just writes the config file from static data.
405 instance_name = instance.name
406 dirs = [(dname, constants.RUN_DIRS_MODE)
407 for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
408 utils.EnsureDirs(dirs)
410 cfg_file = cls._InstanceNICFile(instance_name, idx)
413 data.write("TAGS=%s\n" % "\ ".join(instance.GetTags()))
415 netinfo = objects.Network.FromDict(nic.netinfo)
416 for k, v in netinfo.HooksDict().iteritems():
417 data.write("%s=%s\n" % (k, v))
419 data.write("MAC=%s\n" % nic.mac)
421 data.write("IP=%s\n" % nic.ip)
422 data.write("INTERFACE_INDEX=%s\n" % str(idx))
424 data.write("INTERFACE_NAME=%s\n" % nic.name)
425 data.write("INTERFACE_UUID=%s\n" % nic.uuid)
426 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
427 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
430 utils.WriteFile(cfg_file, data=data.getvalue())
431 except EnvironmentError, err:
432 raise errors.HypervisorError("Cannot write Xen instance configuration"
433 " file %s: %s" % (cfg_file, err))
436 def _InstanceNICDir(cls, instance_name):
437 """Returns the directory holding the tap device files for a given instance.
440 return utils.PathJoin(cls._NICS_DIR, instance_name)
443 def _InstanceNICFile(cls, instance_name, seq):
444 """Returns the name of the file containing the tap device for a given NIC
447 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
450 def _GetConfig(cls, instance, startup_memory, block_devices):
451 """Build Xen configuration for an instance.
454 raise NotImplementedError
456 def _WriteConfigFile(self, instance_name, data):
457 """Write the Xen config file for the instance.
459 This version of the function just writes the config file from static data.
462 # just in case it exists
463 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
465 cfg_file = self._ConfigFileName(instance_name)
467 utils.WriteFile(cfg_file, data=data)
468 except EnvironmentError, err:
469 raise errors.HypervisorError("Cannot write Xen instance configuration"
470 " file %s: %s" % (cfg_file, err))
472 def _ReadConfigFile(self, instance_name):
473 """Returns the contents of the instance config file.
476 filename = self._ConfigFileName(instance_name)
479 file_content = utils.ReadFile(filename)
480 except EnvironmentError, err:
481 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
485 def _RemoveConfigFile(self, instance_name):
486 """Remove the xen configuration file.
489 utils.RemoveFile(self._ConfigFileName(instance_name))
491 shutil.rmtree(self._InstanceNICDir(instance_name))
493 if err.errno != errno.ENOENT:
496 def _StashConfigFile(self, instance_name):
497 """Move the Xen config file to the log directory and return its new path.
500 old_filename = self._ConfigFileName(instance_name)
502 (instance_name, utils.TimestampForFilename()))
503 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
504 utils.RenameFile(old_filename, new_filename)
507 def _GetXmList(self, include_node):
508 """Wrapper around module level L{_GetXmList}.
511 return _GetXmList(lambda: self._RunXen(["list"]), include_node)
513 def ListInstances(self):
514 """Get the list of running instances.
517 xm_list = self._GetXmList(False)
518 names = [info[0] for info in xm_list]
521 def GetInstanceInfo(self, instance_name):
522 """Get instance properties.
524 @param instance_name: the instance name
526 @return: tuple (name, id, memory, vcpus, stat, times)
529 xm_list = self._GetXmList(instance_name == _DOM0_NAME)
532 if data[0] == instance_name:
537 def GetAllInstancesInfo(self):
538 """Get properties of all instances.
540 @return: list of tuples (name, id, memory, vcpus, stat, times)
543 xm_list = self._GetXmList(False)
546 def _MakeConfigFile(self, instance, startup_memory, block_devices):
547 """Gather configuration details and write to disk.
549 See L{_GetConfig} for arguments.
553 buf.write("# Automatically generated by Ganeti. Do not edit!\n")
555 buf.write(self._GetConfig(instance, startup_memory, block_devices))
558 self._WriteConfigFile(instance.name, buf.getvalue())
560 def StartInstance(self, instance, block_devices, startup_paused):
561 """Start an instance.
564 startup_memory = self._InstanceStartupMemory(instance)
566 self._MakeConfigFile(instance, startup_memory, block_devices)
571 cmd.append(self._ConfigFileName(instance.name))
573 result = self._RunXen(cmd)
575 # Move the Xen configuration file to the log directory to avoid
576 # leaving a stale config file behind.
577 stashed_config = self._StashConfigFile(instance.name)
578 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
579 " config file to %s" %
580 (instance.name, result.fail_reason,
581 result.output, stashed_config))
583 def StopInstance(self, instance, force=False, retry=False, name=None,
587 A soft shutdown can be interrupted. A hard shutdown tries forever.
590 assert(timeout is None or force is not None)
595 return self._StopInstance(name, force, timeout)
597 def _ShutdownInstance(self, name, timeout):
598 """Shutdown an instance if the instance is running.
600 The '-w' flag waits for shutdown to complete which avoids the need
601 to poll in the case where we want to destroy the domain
602 immediately after shutdown.
605 @param name: name of the instance to stop
606 @type timeout: int or None
607 @param timeout: a timeout after which the shutdown command should be killed,
608 or None for no timeout
611 instance_info = self.GetInstanceInfo(name)
613 if instance_info is None or _IsInstanceShutdown(instance_info[4]):
614 logging.info("Failed to shutdown instance %s, not running", name)
617 return self._RunXen(["shutdown", "-w", name], timeout)
619 def _DestroyInstance(self, name):
620 """Destroy an instance if the instance if the instance exists.
623 @param name: name of the instance to destroy
626 instance_info = self.GetInstanceInfo(name)
628 if instance_info is None:
629 logging.info("Failed to destroy instance %s, does not exist", name)
632 return self._RunXen(["destroy", name])
634 def _StopInstance(self, name, force, timeout):
638 @param name: name of the instance to destroy
641 @param force: whether to do a "hard" stop (destroy)
643 @type timeout: int or None
644 @param timeout: a timeout after which the shutdown command should be killed,
645 or None for no timeout
649 result = self._DestroyInstance(name)
651 self._ShutdownInstance(name, timeout)
652 result = self._DestroyInstance(name)
654 if result is not None and result.failed and \
655 self.GetInstanceInfo(name) is not None:
656 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
657 (name, result.fail_reason, result.output))
659 # Remove configuration file if stopping/starting instance was successful
660 self._RemoveConfigFile(name)
662 def RebootInstance(self, instance):
663 """Reboot an instance.
666 ini_info = self.GetInstanceInfo(instance.name)
669 raise errors.HypervisorError("Failed to reboot instance %s,"
670 " not running" % instance.name)
672 result = self._RunXen(["reboot", instance.name])
674 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
675 (instance.name, result.fail_reason,
678 def _CheckInstance():
679 new_info = self.GetInstanceInfo(instance.name)
681 # check if the domain ID has changed or the run time has decreased
682 if (new_info is not None and
683 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
686 raise utils.RetryAgain()
689 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
690 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
691 except utils.RetryTimeout:
692 raise errors.HypervisorError("Failed to reboot instance %s: instance"
693 " did not reboot in the expected interval" %
696 def BalloonInstanceMemory(self, instance, mem):
697 """Balloon an instance memory to a certain value.
699 @type instance: L{objects.Instance}
700 @param instance: instance to be accepted
702 @param mem: actual memory size to use for instance runtime
705 result = self._RunXen(["mem-set", instance.name, mem])
707 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
708 (instance.name, result.fail_reason,
711 # Update configuration file
712 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
713 cmd.append(self._ConfigFileName(instance.name))
715 result = utils.RunCmd(cmd)
717 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
718 (instance.name, result.fail_reason,
721 def GetNodeInfo(self):
722 """Return information about the node.
724 @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
727 result = self._RunXen(["info"])
729 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
733 return _GetNodeInfo(result.stdout, self._GetXmList)
736 def GetInstanceConsole(cls, instance, hvparams, beparams):
737 """Return a command for connecting to the console of an instance.
740 return objects.InstanceConsole(instance=instance.name,
741 kind=constants.CONS_SSH,
742 host=instance.primary_node,
743 user=constants.SSH_CONSOLE_USER,
744 command=[pathutils.XEN_CONSOLE_WRAPPER,
745 constants.XEN_CMD, instance.name])
748 """Verify the hypervisor.
750 For Xen, this verifies that the xend process is running.
752 @return: Problem description if something is wrong, C{None} otherwise
755 result = self._RunXen(["info"])
757 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
761 def MigrationInfo(self, instance):
762 """Get instance information to perform a migration.
764 @type instance: L{objects.Instance}
765 @param instance: instance to be migrated
767 @return: content of the xen config file
770 return self._ReadConfigFile(instance.name)
772 def AcceptInstance(self, instance, info, target):
773 """Prepare to accept an instance.
775 @type instance: L{objects.Instance}
776 @param instance: instance to be accepted
778 @param info: content of the xen config file on the source node
780 @param target: target host (usually ip), on this node
785 def FinalizeMigrationDst(self, instance, info, success):
786 """Finalize an instance migration.
788 After a successful migration we write the xen config file.
789 We do nothing on a failure, as we did not change anything at accept time.
791 @type instance: L{objects.Instance}
792 @param instance: instance whose migration is being finalized
794 @param info: content of the xen config file on the source node
795 @type success: boolean
796 @param success: whether the migration was a success or a failure
800 self._WriteConfigFile(instance.name, info)
802 def MigrateInstance(self, instance, target, live):
803 """Migrate an instance to a target node.
805 The migration will not be attempted if the instance is not
808 @type instance: L{objects.Instance}
809 @param instance: the instance to be migrated
811 @param target: ip address of the target node
813 @param live: perform a live migration
816 port = instance.hvparams[constants.HV_MIGRATION_PORT]
818 # TODO: Pass cluster name via RPC
819 cluster_name = ssconf.SimpleStore().GetClusterName()
821 return self._MigrateInstance(cluster_name, instance.name, target, port,
824 def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
825 _ping_fn=netutils.TcpPing):
826 """Migrate an instance to a target node.
828 @see: L{MigrateInstance} for details
831 if self.GetInstanceInfo(instance_name) is None:
832 raise errors.HypervisorError("Instance not running, cannot migrate")
834 cmd = self._GetCommand()
836 if (cmd == constants.XEN_CMD_XM and
837 not _ping_fn(target, port, live_port_needed=True)):
838 raise errors.HypervisorError("Remote host %s not listening on port"
839 " %s, cannot migrate" % (target, port))
843 if cmd == constants.XEN_CMD_XM:
844 args.extend(["-p", "%d" % port])
848 elif cmd == constants.XEN_CMD_XL:
850 "-s", constants.XL_SSH_CMD % cluster_name,
851 "-C", self._ConfigFileName(instance_name),
855 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
857 args.extend([instance_name, target])
859 result = self._RunXen(args)
861 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
862 (instance_name, result.output))
864 def FinalizeMigrationSource(self, instance, success, live):
865 """Finalize the instance migration on the source node.
867 @type instance: L{objects.Instance}
868 @param instance: the instance that was migrated
870 @param success: whether the migration succeeded or not
872 @param live: whether the user requested a live migration or not
875 # pylint: disable=W0613
877 # remove old xen file after migration succeeded
879 self._RemoveConfigFile(instance.name)
880 except EnvironmentError:
881 logging.exception("Failure while removing instance config file")
883 def GetMigrationStatus(self, instance):
884 """Get the migration status
886 As MigrateInstance for Xen is still blocking, if this method is called it
887 means that MigrateInstance has completed successfully. So we can safely
888 assume that the migration was successful and notify this fact to the client.
890 @type instance: L{objects.Instance}
891 @param instance: the instance that is being migrated
892 @rtype: L{objects.MigrationStatus}
893 @return: the status of the current migration (one of
894 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
895 progress info that can be retrieved from the hypervisor
898 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
901 def PowercycleNode(cls):
902 """Xen-specific powercycle.
904 This first does a Linux reboot (which triggers automatically a Xen
905 reboot), and if that fails it tries to do a Xen reboot. The reason
906 we don't try a Xen reboot first is that the xen reboot launches an
907 external command which connects to the Xen hypervisor, and that
908 won't work in case the root filesystem is broken and/or the xend
909 daemon is not working.
913 cls.LinuxPowercycle()
915 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
918 class XenPvmHypervisor(XenHypervisor):
919 """Xen PVM hypervisor interface"""
922 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
923 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
924 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
925 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
926 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
927 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
928 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
929 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
930 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
931 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
932 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
933 constants.HV_REBOOT_BEHAVIOR:
934 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
935 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
936 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
937 constants.HV_CPU_WEIGHT:
938 (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
939 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
942 def _GetConfig(self, instance, startup_memory, block_devices):
943 """Write the Xen config file for the instance.
946 hvp = instance.hvparams
948 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
950 # if bootloader is True, use bootloader instead of kernel and ramdisk
952 if hvp[constants.HV_USE_BOOTLOADER]:
953 # bootloader handling
954 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
956 config.write("bootloader = '%s'\n" % bootloader_path)
958 raise errors.HypervisorError("Bootloader enabled, but missing"
961 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
963 config.write("bootargs = '%s'\n" % bootloader_args)
966 kpath = hvp[constants.HV_KERNEL_PATH]
967 config.write("kernel = '%s'\n" % kpath)
970 initrd_path = hvp[constants.HV_INITRD_PATH]
972 config.write("ramdisk = '%s'\n" % initrd_path)
974 # rest of the settings
975 config.write("memory = %d\n" % startup_memory)
976 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
977 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
978 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
980 config.write("%s\n" % cpu_pinning)
981 cpu_cap = hvp[constants.HV_CPU_CAP]
983 config.write("cpu_cap=%d\n" % cpu_cap)
984 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
986 config.write("cpu_weight=%d\n" % cpu_weight)
988 config.write("name = '%s'\n" % instance.name)
991 for idx, nic in enumerate(instance.nics):
992 nic_str = "mac=%s" % (nic.mac)
993 ip = getattr(nic, "ip", None)
995 nic_str += ", ip=%s" % ip
996 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
997 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
998 if hvp[constants.HV_VIF_SCRIPT]:
999 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1000 vif_data.append("'%s'" % nic_str)
1001 self._WriteNICInfoFile(instance, idx, nic)
1004 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1006 config.write("vif = [%s]\n" % ",".join(vif_data))
1007 config.write("disk = [%s]\n" % ",".join(disk_data))
1009 if hvp[constants.HV_ROOT_PATH]:
1010 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1011 config.write("on_poweroff = 'destroy'\n")
1012 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1013 config.write("on_reboot = 'restart'\n")
1015 config.write("on_reboot = 'destroy'\n")
1016 config.write("on_crash = 'restart'\n")
1017 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1019 return config.getvalue()
1022 class XenHvmHypervisor(XenHypervisor):
1023 """Xen HVM hypervisor interface"""
1025 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1026 pathutils.VNC_PASSWORD_FILE,
1028 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1029 pathutils.VNC_PASSWORD_FILE,
1033 constants.HV_ACPI: hv_base.NO_CHECK,
1034 constants.HV_BOOT_ORDER: (True, ) +
1035 (lambda x: x and len(x.strip("acdn")) == 0,
1036 "Invalid boot order specified, must be one or more of [acdn]",
1038 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1039 constants.HV_DISK_TYPE:
1040 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1041 constants.HV_NIC_TYPE:
1042 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1043 constants.HV_PAE: hv_base.NO_CHECK,
1044 constants.HV_VNC_BIND_ADDRESS:
1045 (False, netutils.IP4Address.IsValid,
1046 "VNC bind address is not a valid IP address", None, None),
1047 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1048 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1049 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1050 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1051 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1052 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1053 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1054 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1055 # Add PCI passthrough
1056 constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1057 constants.HV_REBOOT_BEHAVIOR:
1058 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1059 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1060 constants.HV_CPU_CAP: hv_base.NO_CHECK,
1061 constants.HV_CPU_WEIGHT:
1062 (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1063 constants.HV_VIF_TYPE:
1064 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1065 constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1066 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1069 def _GetConfig(self, instance, startup_memory, block_devices):
1070 """Create a Xen 3.1 HVM config file.
1073 hvp = instance.hvparams
1078 kpath = hvp[constants.HV_KERNEL_PATH]
1079 config.write("kernel = '%s'\n" % kpath)
1081 config.write("builder = 'hvm'\n")
1082 config.write("memory = %d\n" % startup_memory)
1083 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1084 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1085 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1087 config.write("%s\n" % cpu_pinning)
1088 cpu_cap = hvp[constants.HV_CPU_CAP]
1090 config.write("cpu_cap=%d\n" % cpu_cap)
1091 cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1093 config.write("cpu_weight=%d\n" % cpu_weight)
1095 config.write("name = '%s'\n" % instance.name)
1096 if hvp[constants.HV_PAE]:
1097 config.write("pae = 1\n")
1099 config.write("pae = 0\n")
1100 if hvp[constants.HV_ACPI]:
1101 config.write("acpi = 1\n")
1103 config.write("acpi = 0\n")
1104 if hvp[constants.HV_VIRIDIAN]:
1105 config.write("viridian = 1\n")
1107 config.write("viridian = 0\n")
1109 config.write("apic = 1\n")
1110 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1111 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1112 config.write("sdl = 0\n")
1113 config.write("usb = 1\n")
1114 config.write("usbdevice = 'tablet'\n")
1115 config.write("vnc = 1\n")
1116 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1117 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1119 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1121 if instance.network_port > constants.VNC_BASE_PORT:
1122 display = instance.network_port - constants.VNC_BASE_PORT
1123 config.write("vncdisplay = %s\n" % display)
1124 config.write("vncunused = 0\n")
1126 config.write("# vncdisplay = 1\n")
1127 config.write("vncunused = 1\n")
1129 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1131 password = utils.ReadFile(vnc_pwd_file)
1132 except EnvironmentError, err:
1133 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1134 (vnc_pwd_file, err))
1136 config.write("vncpasswd = '%s'\n" % password.rstrip())
1138 config.write("serial = 'pty'\n")
1139 if hvp[constants.HV_USE_LOCALTIME]:
1140 config.write("localtime = 1\n")
1143 # Note: what is called 'nic_type' here, is used as value for the xen nic
1144 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1145 # the 'vif_type' to avoid a clash of notation.
1146 nic_type = hvp[constants.HV_NIC_TYPE]
1148 if nic_type is None:
1150 if hvp[constants.HV_VIF_TYPE]:
1151 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1152 # ensure old instances don't change
1153 nic_type_str = vif_type_str
1154 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1155 nic_type_str = ", type=paravirtualized"
1157 # parameter 'model' is only valid with type 'ioemu'
1158 nic_type_str = ", model=%s, type=%s" % \
1159 (nic_type, constants.HT_HVM_VIF_IOEMU)
1160 for idx, nic in enumerate(instance.nics):
1161 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1162 ip = getattr(nic, "ip", None)
1164 nic_str += ", ip=%s" % ip
1165 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1166 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1167 if hvp[constants.HV_VIF_SCRIPT]:
1168 nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1169 vif_data.append("'%s'" % nic_str)
1170 self._WriteNICInfoFile(instance, idx, nic)
1172 config.write("vif = [%s]\n" % ",".join(vif_data))
1175 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1177 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1179 iso = "'file:%s,hdc:cdrom,r'" % iso_path
1180 disk_data.append(iso)
1182 config.write("disk = [%s]\n" % (",".join(disk_data)))
1183 # Add PCI passthrough
1185 pci_pass = hvp[constants.HV_PASSTHROUGH]
1187 pci_pass_arr = pci_pass.split(";")
1188 config.write("pci = %s\n" % pci_pass_arr)
1189 config.write("on_poweroff = 'destroy'\n")
1190 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1191 config.write("on_reboot = 'restart'\n")
1193 config.write("on_reboot = 'destroy'\n")
1194 config.write("on_crash = 'restart'\n")
1196 return config.getvalue()