Package ganeti :: Package hypervisor :: Module hv_xen
[hide private]
[frames] | no frames]

Source Code for Module ganeti.hypervisor.hv_xen

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc. 
   5  # All rights reserved. 
   6  # 
   7  # Redistribution and use in source and binary forms, with or without 
   8  # modification, are permitted provided that the following conditions are 
   9  # met: 
  10  # 
  11  # 1. Redistributions of source code must retain the above copyright notice, 
  12  # this list of conditions and the following disclaimer. 
  13  # 
  14  # 2. Redistributions in binary form must reproduce the above copyright 
  15  # notice, this list of conditions and the following disclaimer in the 
  16  # documentation and/or other materials provided with the distribution. 
  17  # 
  18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
  19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
  20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  29   
  30   
  31  """Xen hypervisors 
  32   
  33  """ 
  34   
  35  import logging 
  36  import errno 
  37  import string # pylint: disable=W0402 
  38  import shutil 
  39  import time 
  40  from cStringIO import StringIO 
  41   
  42  from ganeti import constants 
  43  from ganeti import errors 
  44  from ganeti import utils 
  45  from ganeti.hypervisor import hv_base 
  46  from ganeti import netutils 
  47  from ganeti import objects 
  48  from ganeti import pathutils 
  49   
  50   
  51  XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp") 
  52  XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf") 
  53  VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR, 
  54                                     "scripts/vif-bridge") 
  55  _DOM0_NAME = "Domain-0" 
  56  _DISK_LETTERS = string.ascii_lowercase 
  57   
  58  _FILE_DRIVER_MAP = { 
  59    constants.FD_LOOP: "file", 
  60    constants.FD_BLKTAP: "tap:aio", 
  61    constants.FD_BLKTAP2: "tap2:tapdisk:aio", 
  62    } 
63 64 65 -def _CreateConfigCpus(cpu_mask):
66 """Create a CPU config string for Xen's config file. 67 68 """ 69 # Convert the string CPU mask to a list of list of int's 70 cpu_list = utils.ParseMultiCpuMask(cpu_mask) 71 72 if len(cpu_list) == 1: 73 all_cpu_mapping = cpu_list[0] 74 if all_cpu_mapping == constants.CPU_PINNING_OFF: 75 # If CPU pinning has 1 entry that's "all", then remove the 76 # parameter from the config file 77 return None 78 else: 79 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire 80 # VM) to one physical CPU, using format 'cpu = "C"' 81 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping)) 82 else: 83 84 def _GetCPUMap(vcpu): 85 if vcpu[0] == constants.CPU_PINNING_ALL_VAL: 86 cpu_map = constants.CPU_PINNING_ALL_XEN 87 else: 88 cpu_map = ",".join(map(str, vcpu)) 89 return "\"%s\"" % cpu_map
90 91 # build the result string in format 'cpus = [ "c", "c", "c" ]', 92 # where each c is a physical CPU number, a range, a list, or any 93 # combination 94 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list)) 95
96 97 -def _RunInstanceList(fn, instance_list_errors):
98 """Helper function for L{_GetAllInstanceList} to retrieve the list 99 of instances from xen. 100 101 @type fn: callable 102 @param fn: Function to query xen for the list of instances 103 @type instance_list_errors: list 104 @param instance_list_errors: Error list 105 @rtype: list 106 107 """ 108 result = fn() 109 if result.failed: 110 logging.error("Retrieving the instance list from xen failed (%s): %s", 111 result.fail_reason, result.output) 112 instance_list_errors.append(result) 113 raise utils.RetryAgain() 114 115 # skip over the heading 116 return result.stdout.splitlines()
117
118 119 -class _InstanceCrashed(errors.GenericError):
120 """Instance has reached a violent ending. 121 122 This is raised within the Xen hypervisor only, and should not be seen or used 123 outside. 124 125 """
126
127 128 -def _ParseInstanceList(lines, include_node):
129 """Parses the output of listing instances by xen. 130 131 @type lines: list 132 @param lines: Result of retrieving the instance list from xen 133 @type include_node: boolean 134 @param include_node: If True, return information for Dom0 135 @return: list of tuple containing (name, id, memory, vcpus, state, time 136 spent) 137 138 """ 139 result = [] 140 141 # Iterate through all lines while ignoring header 142 for line in lines[1:]: 143 # The format of lines is: 144 # Name ID Mem(MiB) VCPUs State Time(s) 145 # Domain-0 0 3418 4 r----- 266.2 146 data = line.split() 147 if len(data) != 6: 148 raise errors.HypervisorError("Can't parse instance list," 149 " line: %s" % line) 150 try: 151 data[1] = int(data[1]) 152 data[2] = int(data[2]) 153 data[3] = int(data[3]) 154 data[4] = _XenToHypervisorInstanceState(data[4]) 155 data[5] = float(data[5]) 156 except (TypeError, ValueError), err: 157 raise errors.HypervisorError("Can't parse instance list," 158 " line: %s, error: %s" % (line, err)) 159 except _InstanceCrashed: 160 # The crashed instance can be interpreted as being down, so we omit it 161 # from the instance list. 162 continue 163 164 # skip the Domain-0 (optional) 165 if include_node or data[0] != _DOM0_NAME: 166 result.append(data) 167 168 return result
169
170 171 -def _GetAllInstanceList(fn, include_node, delays, timeout):
172 """Return the list of instances including running and shutdown. 173 174 See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details. 175 176 """ 177 instance_list_errors = [] 178 try: 179 lines = utils.Retry(_RunInstanceList, delays, timeout, 180 args=(fn, instance_list_errors)) 181 except utils.RetryTimeout: 182 if instance_list_errors: 183 instance_list_result = instance_list_errors.pop() 184 185 errmsg = ("listing instances failed, timeout exceeded (%s): %s" % 186 (instance_list_result.fail_reason, instance_list_result.output)) 187 else: 188 errmsg = "listing instances failed" 189 190 raise errors.HypervisorError(errmsg) 191 192 return _ParseInstanceList(lines, include_node)
193
194 195 -def _IsInstanceRunning(instance_info):
196 """Determine whether an instance is running. 197 198 An instance is running if it is in the following Xen states: 199 running, blocked, paused, or dying (about to be destroyed / shutdown). 200 201 For some strange reason, Xen once printed 'rb----' which does not make any 202 sense because an instance cannot be both running and blocked. Fortunately, 203 for Ganeti 'running' or 'blocked' is the same as 'running'. 204 205 A state of nothing '------' means that the domain is runnable but it is not 206 currently running. That means it is in the queue behind other domains waiting 207 to be scheduled to run. 208 http://old-list-archives.xenproject.org/xen-users/2007-06/msg00849.html 209 210 A dying instance is about to be removed, but it is still consuming resources, 211 and counts as running. 212 213 @type instance_info: string 214 @param instance_info: Information about instance, as supplied by Xen. 215 @rtype: bool 216 @return: Whether an instance is running. 217 218 """ 219 allowable_running_prefixes = [ 220 "r--", 221 "rb-", 222 "-b-", 223 "---", 224 ] 225 226 def _RunningWithSuffix(suffix): 227 return map(lambda x: x + suffix, allowable_running_prefixes)
228 229 # The shutdown suspend ("ss") state is encountered during migration, where 230 # the instance is still considered to be running. 231 # The shutdown restart ("sr") is probably encountered during restarts - still 232 # running. 233 # See Xen commit e1475a6693aac8cddc4bdd456548aa05a625556b 234 return instance_info in _RunningWithSuffix("---") \ 235 or instance_info in _RunningWithSuffix("ss-") \ 236 or instance_info in _RunningWithSuffix("sr-") \ 237 or instance_info == "-----d" 238
239 240 -def _IsInstanceShutdown(instance_info):
241 """Determine whether the instance is shutdown. 242 243 An instance is shutdown when a user shuts it down from within, and we do not 244 remove domains to be able to detect that. 245 246 The dying state has been added as a precaution, as Xen's status reporting is 247 weird. 248 249 """ 250 return instance_info == "---s--" \ 251 or instance_info == "---s-d"
252
253 254 -def _IgnorePaused(instance_info):
255 """Removes information about whether a Xen state is paused from the state. 256 257 As it turns out, an instance can be reported as paused in almost any 258 condition. Paused instances can be paused, running instances can be paused for 259 scheduling, and any other condition can appear to be paused as a result of 260 races or improbable conditions in Xen's status reporting. 261 As we do not use Xen's pause commands in any way at the time, we can simply 262 ignore the paused field and save ourselves a lot of trouble. 263 264 Should we ever use the pause commands, several samples would be needed before 265 we could confirm the domain as paused. 266 267 """ 268 return instance_info.replace('p', '-')
269
270 271 -def _IsCrashed(instance_info):
272 """Returns whether an instance is in the crashed Xen state. 273 274 When a horrible misconfiguration happens to a Xen domain, it can crash, 275 meaning that it encounters a violent ending. While this state usually flashes 276 only temporarily before the domain is restarted, being able to check for it 277 allows Ganeti not to act confused and do something about it. 278 279 """ 280 return instance_info.count('c') > 0
281
282 283 -def _XenToHypervisorInstanceState(instance_info):
284 """Maps Xen states to hypervisor states. 285 286 @type instance_info: string 287 @param instance_info: Information about instance, as supplied by Xen. 288 @rtype: L{hv_base.HvInstanceState} 289 290 """ 291 instance_info = _IgnorePaused(instance_info) 292 293 if _IsCrashed(instance_info): 294 raise _InstanceCrashed("Instance detected as crashed, should be omitted") 295 296 if _IsInstanceRunning(instance_info): 297 return hv_base.HvInstanceState.RUNNING 298 elif _IsInstanceShutdown(instance_info): 299 return hv_base.HvInstanceState.SHUTDOWN 300 else: 301 raise errors.HypervisorError("hv_xen._XenToHypervisorInstanceState:" 302 " unhandled Xen instance state '%s'" % 303 instance_info)
304
305 306 -def _GetRunningInstanceList(fn, include_node, delays, timeout):
307 """Return the list of running instances. 308 309 See L{_GetAllInstanceList} for parameter details. 310 311 """ 312 instances = _GetAllInstanceList(fn, include_node, delays, timeout) 313 return [i for i in instances if hv_base.HvInstanceState.IsRunning(i[4])]
314
315 316 -def _GetShutdownInstanceList(fn, include_node, delays, timeout):
317 """Return the list of shutdown instances. 318 319 See L{_GetAllInstanceList} for parameter details. 320 321 """ 322 instances = _GetAllInstanceList(fn, include_node, delays, timeout) 323 return [i for i in instances if hv_base.HvInstanceState.IsShutdown(i[4])]
324
325 326 -def _ParseNodeInfo(info):
327 """Return information about the node. 328 329 @return: a dict with the following keys (memory values in MiB): 330 - memory_total: the total memory size on the node 331 - memory_free: the available memory on the node for instances 332 - nr_cpus: total number of CPUs 333 - nr_nodes: in a NUMA system, the number of domains 334 - nr_sockets: the number of physical CPU sockets in the node 335 - hv_version: the hypervisor version in the form (major, minor) 336 337 """ 338 result = {} 339 cores_per_socket = threads_per_core = nr_cpus = None 340 xen_major, xen_minor = None, None 341 memory_total = None 342 memory_free = None 343 344 for line in info.splitlines(): 345 fields = line.split(":", 1) 346 347 if len(fields) < 2: 348 continue 349 350 (key, val) = map(lambda s: s.strip(), fields) 351 352 # Note: in Xen 3, memory has changed to total_memory 353 if key in ("memory", "total_memory"): 354 memory_total = int(val) 355 elif key == "free_memory": 356 memory_free = int(val) 357 elif key == "nr_cpus": 358 nr_cpus = result["cpu_total"] = int(val) 359 elif key == "nr_nodes": 360 result["cpu_nodes"] = int(val) 361 elif key == "cores_per_socket": 362 cores_per_socket = int(val) 363 elif key == "threads_per_core": 364 threads_per_core = int(val) 365 elif key == "xen_major": 366 xen_major = int(val) 367 elif key == "xen_minor": 368 xen_minor = int(val) 369 370 if None not in [cores_per_socket, threads_per_core, nr_cpus]: 371 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core) 372 373 if memory_free is not None: 374 result["memory_free"] = memory_free 375 376 if memory_total is not None: 377 result["memory_total"] = memory_total 378 379 if not (xen_major is None or xen_minor is None): 380 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor) 381 382 return result
383
384 385 -def _MergeInstanceInfo(info, instance_list):
386 """Updates node information from L{_ParseNodeInfo} with instance info. 387 388 @type info: dict 389 @param info: Result from L{_ParseNodeInfo} 390 @type instance_list: list of tuples 391 @param instance_list: list of instance information; one tuple per instance 392 @rtype: dict 393 394 """ 395 total_instmem = 0 396 397 for (name, _, mem, vcpus, _, _) in instance_list: 398 if name == _DOM0_NAME: 399 info["memory_dom0"] = mem 400 info["cpu_dom0"] = vcpus 401 402 # Include Dom0 in total memory usage 403 total_instmem += mem 404 405 memory_free = info.get("memory_free") 406 memory_total = info.get("memory_total") 407 408 # Calculate memory used by hypervisor 409 if None not in [memory_total, memory_free, total_instmem]: 410 info["memory_hv"] = memory_total - memory_free - total_instmem 411 412 return info
413
414 415 -def _GetNodeInfo(info, instance_list):
416 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}. 417 418 @type instance_list: list of tuples 419 @param instance_list: list of instance information; one tuple per instance 420 421 """ 422 return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
423
424 425 -def _GetConfigFileDiskData(block_devices, blockdev_prefix, 426 _letters=_DISK_LETTERS):
427 """Get disk directives for Xen config file. 428 429 This method builds the xen config disk directive according to the 430 given disk_template and block_devices. 431 432 @param block_devices: list of tuples (cfdev, rldev): 433 - cfdev: dict containing ganeti config disk part 434 - rldev: ganeti.block.bdev.BlockDev object 435 @param blockdev_prefix: a string containing blockdevice prefix, 436 e.g. "sd" for /dev/sda 437 438 @return: string containing disk directive for xen instance config file 439 440 """ 441 if len(block_devices) > len(_letters): 442 raise errors.HypervisorError("Too many disks") 443 444 disk_data = [] 445 446 for sd_suffix, (cfdev, dev_path, _) in zip(_letters, block_devices): 447 sd_name = blockdev_prefix + sd_suffix 448 449 if cfdev.mode == constants.DISK_RDWR: 450 mode = "w" 451 else: 452 mode = "r" 453 454 if cfdev.dev_type in constants.DTS_FILEBASED: 455 driver = _FILE_DRIVER_MAP[cfdev.logical_id[0]] 456 else: 457 driver = "phy" 458 459 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode)) 460 461 return disk_data
462
463 464 -def _QuoteCpuidField(data):
465 """Add quotes around the CPUID field only if necessary. 466 467 Xen CPUID fields come in two shapes: LIBXL strings, which need quotes around 468 them, and lists of XEND strings, which don't. 469 470 @param data: Either type of parameter. 471 @return: The quoted version thereof. 472 473 """ 474 return "'%s'" % data if data.startswith("host") else data
475
476 477 -def _ConfigureNIC(instance, seq, nic, tap):
478 """Run the network configuration script for a specified NIC 479 480 See L{hv_base.ConfigureNIC}. 481 482 @type instance: instance object 483 @param instance: instance we're acting on 484 @type seq: int 485 @param seq: nic sequence number 486 @type nic: nic object 487 @param nic: nic we're acting on 488 @type tap: str 489 @param tap: the host's tap interface this NIC corresponds to 490 491 """ 492 hv_base.ConfigureNIC(pathutils.XEN_IFUP_OS, instance, seq, nic, tap)
493
494 495 -class XenHypervisor(hv_base.BaseHypervisor):
496 """Xen generic hypervisor interface 497 498 This is the Xen base class used for both Xen PVM and HVM. It contains 499 all the functionality that is identical for both. 500 501 """ 502 CAN_MIGRATE = True 503 REBOOT_RETRY_COUNT = 60 504 REBOOT_RETRY_INTERVAL = 10 505 _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor" 506 # contains NICs' info 507 _NICS_DIR = _ROOT_DIR + "/nic" 508 # contains the pidfiles of socat processes used to migrate instaces under xl 509 _MIGRATION_DIR = _ROOT_DIR + "/migration" 510 _DIRS = [_ROOT_DIR, _NICS_DIR, _MIGRATION_DIR] 511 512 _INSTANCE_LIST_DELAYS = (0.3, 1.5, 1.0) 513 _INSTANCE_LIST_TIMEOUT = 5 514 515 ANCILLARY_FILES = [ 516 XEND_CONFIG_FILE, 517 XL_CONFIG_FILE, 518 VIF_BRIDGE_SCRIPT, 519 ] 520 ANCILLARY_FILES_OPT = [ 521 XEND_CONFIG_FILE, 522 XL_CONFIG_FILE, 523 ] 524
525 - def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
526 hv_base.BaseHypervisor.__init__(self) 527 528 if _cfgdir is None: 529 self._cfgdir = pathutils.XEN_CONFIG_DIR 530 else: 531 self._cfgdir = _cfgdir 532 533 if _run_cmd_fn is None: 534 self._run_cmd_fn = utils.RunCmd 535 else: 536 self._run_cmd_fn = _run_cmd_fn 537 538 self._cmd = _cmd
539 540 @staticmethod
541 - def _GetCommandFromHvparams(hvparams):
542 """Returns the Xen command extracted from the given hvparams. 543 544 @type hvparams: dict of strings 545 @param hvparams: hypervisor parameters 546 547 """ 548 if hvparams is None or constants.HV_XEN_CMD not in hvparams: 549 raise errors.HypervisorError("Cannot determine xen command.") 550 else: 551 return hvparams[constants.HV_XEN_CMD]
552
553 - def _GetCommand(self, hvparams):
554 """Returns Xen command to use. 555 556 @type hvparams: dict of strings 557 @param hvparams: hypervisor parameters 558 559 """ 560 if self._cmd is None: 561 cmd = XenHypervisor._GetCommandFromHvparams(hvparams) 562 else: 563 cmd = self._cmd 564 565 if cmd not in constants.KNOWN_XEN_COMMANDS: 566 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd) 567 568 return cmd
569
570 - def _RunXen(self, args, hvparams, timeout=None):
571 """Wrapper around L{utils.process.RunCmd} to run Xen command. 572 573 @type hvparams: dict of strings 574 @param hvparams: dictionary of hypervisor params 575 @type timeout: int or None 576 @param timeout: if a timeout (in seconds) is specified, the command will be 577 terminated after that number of seconds. 578 @see: L{utils.process.RunCmd} 579 580 """ 581 cmd = [] 582 583 if timeout is not None: 584 cmd.extend(["timeout", str(timeout)]) 585 586 cmd.extend([self._GetCommand(hvparams)]) 587 cmd.extend(args) 588 589 return self._run_cmd_fn(cmd)
590
591 - def _ConfigFileName(self, instance_name):
592 """Get the config file name for an instance. 593 594 @param instance_name: instance name 595 @type instance_name: str 596 @return: fully qualified path to instance config file 597 @rtype: str 598 599 """ 600 return utils.PathJoin(self._cfgdir, instance_name)
601 602 @classmethod
603 - def _EnsureDirs(cls, extra_dirs=None):
604 """Makes sure that the directories needed by the hypervisor exist. 605 606 @type extra_dirs: list of string or None 607 @param extra_dirs: Additional directories which ought to exist. 608 609 """ 610 if extra_dirs is None: 611 extra_dirs = [] 612 dirs = [(dname, constants.RUN_DIRS_MODE) for dname in 613 (cls._DIRS + extra_dirs)] 614 utils.EnsureDirs(dirs)
615 616 @classmethod
617 - def _WriteNICInfoFile(cls, instance, idx, nic):
618 """Write the Xen config file for the instance. 619 620 This version of the function just writes the config file from static data. 621 622 """ 623 instance_name = instance.name 624 cls._EnsureDirs(extra_dirs=[cls._InstanceNICDir(instance_name)]) 625 626 cfg_file = cls._InstanceNICFile(instance_name, idx) 627 data = StringIO() 628 629 data.write("TAGS=\"%s\"\n" % r"\ ".join(instance.GetTags())) 630 if nic.netinfo: 631 netinfo = objects.Network.FromDict(nic.netinfo) 632 for k, v in netinfo.HooksDict().iteritems(): 633 data.write("%s=\"%s\"\n" % (k, v)) 634 635 data.write("MAC=%s\n" % nic.mac) 636 if nic.ip: 637 data.write("IP=%s\n" % nic.ip) 638 data.write("INTERFACE_INDEX=%s\n" % str(idx)) 639 if nic.name: 640 data.write("INTERFACE_NAME=%s\n" % nic.name) 641 data.write("INTERFACE_UUID=%s\n" % nic.uuid) 642 data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE]) 643 data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK]) 644 data.write("VLAN=%s\n" % nic.nicparams[constants.NIC_VLAN]) 645 646 try: 647 utils.WriteFile(cfg_file, data=data.getvalue()) 648 except EnvironmentError, err: 649 raise errors.HypervisorError("Cannot write Xen instance configuration" 650 " file %s: %s" % (cfg_file, err))
651 652 @staticmethod
653 - def VersionsSafeForMigration(src, target):
654 """Decide if migration is likely to suceed for hypervisor versions. 655 656 Given two versions of a hypervisor, give a guess whether live migration 657 from the one version to the other version is likely to succeed. For Xen, 658 the heuristics is, that an increase by one on the second digit is OK. This 659 fits with the current numbering scheme. 660 661 @type src: list or tuple 662 @type target: list or tuple 663 @rtype: bool 664 """ 665 if src == target: 666 return True 667 668 if len(src) < 2 or len(target) < 2: 669 return False 670 671 return src[0] == target[0] and target[1] in [src[1], src[1] + 1]
672 673 @classmethod
674 - def _InstanceNICDir(cls, instance_name):
675 """Returns the directory holding the tap device files for a given instance. 676 677 """ 678 return utils.PathJoin(cls._NICS_DIR, instance_name)
679 680 @classmethod
681 - def _InstanceNICFile(cls, instance_name, seq):
682 """Returns the name of the file containing the tap device for a given NIC 683 684 """ 685 return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
686 687 @classmethod
688 - def _InstanceMigrationPidfile(cls, _instance_name):
689 """Returns the name of the pid file for a socat process used to migrate. 690 691 """ 692 #TODO(riba): At the moment, we are using a single pidfile because we 693 # use a single port for migrations at the moment. This is because we do not 694 # allow more migrations, so dynamic port selection and the needed port 695 # modifications are not needed. 696 # The _instance_name parameter has been left here for future use. 697 return utils.PathJoin(cls._MIGRATION_DIR, constants.XL_MIGRATION_PIDFILE)
698 699 @classmethod
700 - def _GetConfig(cls, instance, startup_memory, block_devices):
701 """Build Xen configuration for an instance. 702 703 """ 704 raise NotImplementedError
705
706 - def _WriteNicConfig(self, config, instance, hvp):
707 vif_data = [] 708 709 # only XenHvmHypervisor has these hvparams 710 nic_type = hvp.get(constants.HV_NIC_TYPE, None) 711 vif_type = hvp.get(constants.HV_VIF_TYPE, None) 712 nic_type_str = "" 713 if nic_type or vif_type: 714 if nic_type is None: 715 if vif_type: 716 nic_type_str = ", type=%s" % vif_type 717 elif nic_type == constants.HT_NIC_PARAVIRTUAL: 718 nic_type_str = ", type=paravirtualized" 719 else: 720 # parameter 'model' is only valid with type 'ioemu' 721 nic_type_str = ", model=%s, type=%s" % \ 722 (nic_type, constants.HT_HVM_VIF_IOEMU) 723 724 for idx, nic in enumerate(instance.nics): 725 nic_args = {} 726 nic_args["mac"] = "%s%s" % (nic.mac, nic_type_str) 727 728 if nic.name and \ 729 nic.name.startswith(constants.INSTANCE_COMMUNICATION_NIC_PREFIX): 730 tap = hv_base.GenerateTapName() 731 nic_args["vifname"] = tap 732 nic_args["script"] = pathutils.XEN_VIF_METAD_SETUP 733 nic.name = tap 734 else: 735 ip = getattr(nic, "ip", None) 736 if ip is not None: 737 nic_args["ip"] = ip 738 739 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 740 nic_args["bridge"] = nic.nicparams[constants.NIC_LINK] 741 elif nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS: 742 nic_args["bridge"] = nic.nicparams[constants.NIC_LINK] 743 if nic.nicparams[constants.NIC_VLAN]: 744 nic_args["bridge"] += nic.nicparams[constants.NIC_VLAN] 745 746 if hvp[constants.HV_VIF_SCRIPT]: 747 nic_args["script"] = hvp[constants.HV_VIF_SCRIPT] 748 749 nic_str = ", ".join(["%s=%s" % p for p in nic_args.items()]) 750 vif_data.append("'%s'" % (nic_str, )) 751 self._WriteNICInfoFile(instance, idx, nic) 752 753 config.write("vif = [%s]\n" % ",".join(vif_data))
754
755 - def _WriteConfigFile(self, instance_name, data):
756 """Write the Xen config file for the instance. 757 758 This version of the function just writes the config file from static data. 759 760 """ 761 # just in case it exists 762 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name)) 763 764 cfg_file = self._ConfigFileName(instance_name) 765 try: 766 utils.WriteFile(cfg_file, data=data) 767 except EnvironmentError, err: 768 raise errors.HypervisorError("Cannot write Xen instance configuration" 769 " file %s: %s" % (cfg_file, err))
770
771 - def _ReadConfigFile(self, instance_name):
772 """Returns the contents of the instance config file. 773 774 """ 775 filename = self._ConfigFileName(instance_name) 776 777 try: 778 file_content = utils.ReadFile(filename) 779 except EnvironmentError, err: 780 raise errors.HypervisorError("Failed to load Xen config file: %s" % err) 781 782 return file_content
783
784 - def _RemoveConfigFile(self, instance_name):
785 """Remove the xen configuration file. 786 787 """ 788 utils.RemoveFile(self._ConfigFileName(instance_name)) 789 try: 790 shutil.rmtree(self._InstanceNICDir(instance_name)) 791 except OSError, err: 792 if err.errno != errno.ENOENT: 793 raise
794
795 - def _StashConfigFile(self, instance_name):
796 """Move the Xen config file to the log directory and return its new path. 797 798 """ 799 old_filename = self._ConfigFileName(instance_name) 800 base = ("%s-%s" % 801 (instance_name, utils.TimestampForFilename())) 802 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base) 803 utils.RenameFile(old_filename, new_filename) 804 return new_filename
805
806 - def _GetInstanceList(self, include_node, hvparams):
807 """Wrapper around module level L{_GetAllInstanceList}. 808 809 @type hvparams: dict of strings 810 @param hvparams: hypervisor parameters to be used on this node 811 812 """ 813 return _GetAllInstanceList(lambda: self._RunXen(["list"], hvparams), 814 include_node, delays=self._INSTANCE_LIST_DELAYS, 815 timeout=self._INSTANCE_LIST_TIMEOUT)
816
817 - def ListInstances(self, hvparams=None):
818 """Get the list of running instances. 819 820 @type hvparams: dict of strings 821 @param hvparams: the instance's hypervisor params 822 823 @rtype: list of strings 824 @return: names of running instances 825 826 """ 827 instance_list = _GetRunningInstanceList( 828 lambda: self._RunXen(["list"], hvparams), 829 False, delays=self._INSTANCE_LIST_DELAYS, 830 timeout=self._INSTANCE_LIST_TIMEOUT) 831 return [info[0] for info in instance_list]
832
833 - def GetInstanceInfo(self, instance_name, hvparams=None):
834 """Get instance properties. 835 836 @type instance_name: string 837 @param instance_name: the instance name 838 @type hvparams: dict of strings 839 @param hvparams: the instance's hypervisor params 840 841 @return: tuple (name, id, memory, vcpus, stat, times) 842 843 """ 844 instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams) 845 result = None 846 for data in instance_list: 847 if data[0] == instance_name: 848 result = data 849 break 850 return result
851
852 - def GetAllInstancesInfo(self, hvparams=None):
853 """Get properties of all instances. 854 855 @type hvparams: dict of strings 856 @param hvparams: hypervisor parameters 857 858 @rtype: (string, string, int, int, HypervisorInstanceState, int) 859 @return: list of tuples (name, id, memory, vcpus, state, times) 860 861 """ 862 return self._GetInstanceList(False, hvparams)
863
864 - def _MakeConfigFile(self, instance, startup_memory, block_devices):
865 """Gather configuration details and write to disk. 866 867 See L{_GetConfig} for arguments. 868 869 """ 870 buf = StringIO() 871 buf.write("# Automatically generated by Ganeti. Do not edit!\n") 872 buf.write("\n") 873 buf.write(self._GetConfig(instance, startup_memory, block_devices)) 874 buf.write("\n") 875 876 self._WriteConfigFile(instance.name, buf.getvalue())
877
878 - def StartInstance(self, instance, block_devices, startup_paused):
879 """Start an instance. 880 881 """ 882 startup_memory = self._InstanceStartupMemory(instance) 883 884 self._MakeConfigFile(instance, startup_memory, block_devices) 885 886 cmd = ["create"] 887 if startup_paused: 888 cmd.append("-p") 889 cmd.append(self._ConfigFileName(instance.name)) 890 891 result = self._RunXen(cmd, instance.hvparams) 892 if result.failed: 893 # Move the Xen configuration file to the log directory to avoid 894 # leaving a stale config file behind. 895 stashed_config = self._StashConfigFile(instance.name) 896 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved" 897 " config file to %s" % 898 (instance.name, result.fail_reason, 899 result.output, stashed_config)) 900 901 for nic_seq, nic in enumerate(instance.nics): 902 if nic.name and nic.name.startswith("gnt.com."): 903 _ConfigureNIC(instance, nic_seq, nic, nic.name)
904
905 - def StopInstance(self, instance, force=False, retry=False, name=None, 906 timeout=None):
907 """Stop an instance. 908 909 A soft shutdown can be interrupted. A hard shutdown tries forever. 910 911 """ 912 assert(timeout is None or force is not None) 913 914 if name is None: 915 name = instance.name 916 917 return self._StopInstance(name, force, instance.hvparams, timeout)
918
919 - def _ShutdownInstance(self, name, hvparams, timeout):
920 """Shutdown an instance if the instance is running. 921 922 The '-w' flag waits for shutdown to complete which avoids the need 923 to poll in the case where we want to destroy the domain 924 immediately after shutdown. 925 926 @type name: string 927 @param name: name of the instance to stop 928 @type hvparams: dict of string 929 @param hvparams: hypervisor parameters of the instance 930 @type timeout: int or None 931 @param timeout: a timeout after which the shutdown command should be killed, 932 or None for no timeout 933 934 """ 935 instance_info = self.GetInstanceInfo(name, hvparams=hvparams) 936 937 if instance_info is None or _IsInstanceShutdown(instance_info[4]): 938 logging.info("Failed to shutdown instance %s, not running", name) 939 return None 940 941 return self._RunXen(["shutdown", "-w", name], hvparams, timeout)
942
943 - def _DestroyInstance(self, name, hvparams):
944 """Destroy an instance if the instance if the instance exists. 945 946 @type name: string 947 @param name: name of the instance to destroy 948 @type hvparams: dict of string 949 @param hvparams: hypervisor parameters of the instance 950 951 """ 952 instance_info = self.GetInstanceInfo(name, hvparams=hvparams) 953 954 if instance_info is None: 955 logging.info("Failed to destroy instance %s, does not exist", name) 956 return None 957 958 return self._RunXen(["destroy", name], hvparams)
959 960 # Destroy a domain only if necessary 961 # 962 # This method checks if the domain has already been destroyed before 963 # issuing the 'destroy' command. This step is necessary to handle 964 # domains created by other versions of Ganeti. For example, an 965 # instance created with 2.10 will be destroy by the 966 # '_ShutdownInstance', thus not requiring an additional destroy, 967 # which would cause an error if issued. See issue 619.
968 - def _DestroyInstanceIfAlive(self, name, hvparams):
969 instance_info = self.GetInstanceInfo(name, hvparams=hvparams) 970 971 if instance_info is None: 972 raise errors.HypervisorError("Failed to destroy instance %s, already" 973 " destroyed" % name) 974 else: 975 self._DestroyInstance(name, hvparams)
976
977 - def _StopInstance(self, name, force, hvparams, timeout):
978 """Stop an instance. 979 980 @type name: string 981 @param name: name of the instance to destroy 982 983 @type force: boolean 984 @param force: whether to do a "hard" stop (destroy) 985 986 @type hvparams: dict of string 987 @param hvparams: hypervisor parameters of the instance 988 989 @type timeout: int or None 990 @param timeout: a timeout after which the shutdown command should be killed, 991 or None for no timeout 992 993 """ 994 instance_info = self.GetInstanceInfo(name, hvparams=hvparams) 995 996 if instance_info is None: 997 raise errors.HypervisorError("Failed to shutdown instance %s," 998 " not running" % name) 999 1000 if force: 1001 result = self._DestroyInstanceIfAlive(name, hvparams) 1002 else: 1003 self._ShutdownInstance(name, hvparams, timeout) 1004 result = self._DestroyInstanceIfAlive(name, hvparams) 1005 1006 if result is not None and result.failed and \ 1007 self.GetInstanceInfo(name, hvparams=hvparams) is not None: 1008 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" % 1009 (name, result.fail_reason, result.output)) 1010 1011 # Remove configuration file if stopping/starting instance was successful 1012 self._RemoveConfigFile(name)
1013
1014 - def RebootInstance(self, instance):
1015 """Reboot an instance. 1016 1017 """ 1018 ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams) 1019 1020 if ini_info is None: 1021 raise errors.HypervisorError("Failed to reboot instance %s," 1022 " not running" % instance.name) 1023 1024 result = self._RunXen(["reboot", instance.name], instance.hvparams) 1025 if result.failed: 1026 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" % 1027 (instance.name, result.fail_reason, 1028 result.output)) 1029 1030 def _CheckInstance(): 1031 new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams) 1032 1033 # check if the domain ID has changed or the run time has decreased 1034 if (new_info is not None and 1035 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])): 1036 return 1037 1038 raise utils.RetryAgain()
1039 1040 try: 1041 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL, 1042 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT) 1043 except utils.RetryTimeout: 1044 raise errors.HypervisorError("Failed to reboot instance %s: instance" 1045 " did not reboot in the expected interval" % 1046 (instance.name, ))
1047
1048 - def BalloonInstanceMemory(self, instance, mem):
1049 """Balloon an instance memory to a certain value. 1050 1051 @type instance: L{objects.Instance} 1052 @param instance: instance to be accepted 1053 @type mem: int 1054 @param mem: actual memory size to use for instance runtime 1055 1056 """ 1057 result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams) 1058 if result.failed: 1059 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" % 1060 (instance.name, result.fail_reason, 1061 result.output)) 1062 1063 # Update configuration file 1064 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem] 1065 cmd.append(self._ConfigFileName(instance.name)) 1066 1067 result = utils.RunCmd(cmd) 1068 if result.failed: 1069 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" % 1070 (instance.name, result.fail_reason, 1071 result.output))
1072
1073 - def GetNodeInfo(self, hvparams=None):
1074 """Return information about the node. 1075 1076 @see: L{_GetNodeInfo} and L{_ParseNodeInfo} 1077 1078 """ 1079 result = self._RunXen(["info"], hvparams) 1080 if result.failed: 1081 logging.error("Can't retrieve xen hypervisor information (%s): %s", 1082 result.fail_reason, result.output) 1083 return None 1084 1085 instance_list = self._GetInstanceList(True, hvparams) 1086 return _GetNodeInfo(result.stdout, instance_list)
1087 1088 @classmethod
1089 - def GetInstanceConsole(cls, instance, primary_node, node_group, 1090 hvparams, beparams):
1091 """Return a command for connecting to the console of an instance. 1092 1093 """ 1094 xen_cmd = XenHypervisor._GetCommandFromHvparams(hvparams) 1095 ndparams = node_group.FillND(primary_node) 1096 return objects.InstanceConsole(instance=instance.name, 1097 kind=constants.CONS_SSH, 1098 host=primary_node.name, 1099 port=ndparams.get(constants.ND_SSH_PORT), 1100 user=constants.SSH_CONSOLE_USER, 1101 command=[pathutils.XEN_CONSOLE_WRAPPER, 1102 xen_cmd, instance.name])
1103
1104 - def Verify(self, hvparams=None):
1105 """Verify the hypervisor. 1106 1107 For Xen, this verifies that the xend process is running. 1108 1109 @type hvparams: dict of strings 1110 @param hvparams: hypervisor parameters to be verified against 1111 1112 @return: Problem description if something is wrong, C{None} otherwise 1113 1114 """ 1115 if hvparams is None: 1116 return "Could not verify the hypervisor, because no hvparams were" \ 1117 " provided." 1118 1119 if constants.HV_XEN_CMD in hvparams: 1120 xen_cmd = hvparams[constants.HV_XEN_CMD] 1121 try: 1122 self._CheckToolstack(xen_cmd) 1123 except errors.HypervisorError: 1124 return "The configured xen toolstack '%s' is not available on this" \ 1125 " node." % xen_cmd 1126 1127 result = self._RunXen(["info"], hvparams) 1128 if result.failed: 1129 return "Retrieving information from xen failed: %s, %s" % \ 1130 (result.fail_reason, result.output) 1131 1132 return None
1133
1134 - def MigrationInfo(self, instance):
1135 """Get instance information to perform a migration. 1136 1137 @type instance: L{objects.Instance} 1138 @param instance: instance to be migrated 1139 @rtype: string 1140 @return: content of the xen config file 1141 1142 """ 1143 return self._ReadConfigFile(instance.name)
1144
1145 - def _UseMigrationDaemon(self, hvparams):
1146 """Whether to start a socat daemon when accepting an instance. 1147 1148 @rtype: bool 1149 1150 """ 1151 return self._GetCommand(hvparams) == constants.XEN_CMD_XL
1152 1153 @classmethod
1154 - def _KillMigrationDaemon(cls, instance):
1155 """Kills the migration daemon if present. 1156 1157 """ 1158 pidfile = cls._InstanceMigrationPidfile(instance.name) 1159 read_pid = utils.ReadPidFile(pidfile) 1160 1161 # There is no pidfile, hence nothing for us to do 1162 if read_pid == 0: 1163 return 1164 1165 if utils.IsProcessAlive(read_pid): 1166 # If the process is alive, let's make sure we are killing the right one 1167 cmdline = ' '.join(utils.GetProcCmdline(read_pid)) 1168 if cmdline.count("xl migrate-receive") > 0: 1169 utils.KillProcess(read_pid) 1170 1171 # By this point the process is not running, whether killed or initially 1172 # nonexistent, so it is safe to remove the pidfile. 1173 utils.RemoveFile(pidfile)
1174
1175 - def AcceptInstance(self, instance, info, target):
1176 """Prepare to accept an instance. 1177 1178 @type instance: L{objects.Instance} 1179 @param instance: instance to be accepted 1180 @type info: string 1181 @param info: content of the xen config file on the source node 1182 @type target: string 1183 @param target: target host (usually ip), on this node 1184 1185 """ 1186 if self._UseMigrationDaemon(instance.hvparams): 1187 port = instance.hvparams[constants.HV_MIGRATION_PORT] 1188 1189 # Make sure there is somewhere to put the pidfile. 1190 XenHypervisor._EnsureDirs() 1191 pidfile = XenHypervisor._InstanceMigrationPidfile(instance.name) 1192 1193 # And try and kill a previous daemon 1194 XenHypervisor._KillMigrationDaemon(instance) 1195 1196 listening_arg = "TCP-LISTEN:%d,bind=%s,reuseaddr" % (port, target) 1197 socat_pid = utils.StartDaemon(["socat", "-b524288", listening_arg, 1198 "SYSTEM:'xl migrate-receive'"], 1199 pidfile=pidfile) 1200 1201 # Wait for a while to make sure the socat process has successfully started 1202 # listening 1203 time.sleep(1) 1204 if not utils.IsProcessAlive(socat_pid): 1205 raise errors.HypervisorError("Could not start receiving socat process" 1206 " on port %d: check if port is available" % 1207 port)
1208
1209 - def FinalizeMigrationDst(self, instance, info, success):
1210 """Finalize an instance migration. 1211 1212 After a successful migration we write the xen config file. 1213 We do nothing on a failure, as we did not change anything at accept time. 1214 1215 @type instance: L{objects.Instance} 1216 @param instance: instance whose migration is being finalized 1217 @type info: string 1218 @param info: content of the xen config file on the source node 1219 @type success: boolean 1220 @param success: whether the migration was a success or a failure 1221 1222 """ 1223 if success: 1224 self._WriteConfigFile(instance.name, info) 1225 elif self._UseMigrationDaemon(instance.hvparams): 1226 XenHypervisor._KillMigrationDaemon(instance)
1227
1228 - def MigrateInstance(self, _cluster_name, instance, target, live):
1229 """Migrate an instance to a target node. 1230 1231 The migration will not be attempted if the instance is not 1232 currently running. 1233 1234 @type instance: L{objects.Instance} 1235 @param instance: the instance to be migrated 1236 @type target: string 1237 @param target: ip address of the target node 1238 @type live: boolean 1239 @param live: perform a live migration 1240 1241 """ 1242 port = instance.hvparams[constants.HV_MIGRATION_PORT] 1243 1244 return self._MigrateInstance(instance.name, target, port, live, 1245 instance.hvparams)
1246
1247 - def _MigrateInstance(self, instance_name, target, port, live, hvparams, 1248 _ping_fn=netutils.TcpPing):
1249 """Migrate an instance to a target node. 1250 1251 @see: L{MigrateInstance} for details 1252 1253 """ 1254 if hvparams is None: 1255 raise errors.HypervisorError("No hvparams provided.") 1256 1257 if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None: 1258 raise errors.HypervisorError("Instance not running, cannot migrate") 1259 1260 cmd = self._GetCommand(hvparams) 1261 1262 args = ["migrate"] 1263 1264 if cmd == constants.XEN_CMD_XM: 1265 # Try and see if xm is listening on the specified port 1266 if not _ping_fn(target, port, live_port_needed=True): 1267 raise errors.HypervisorError("Remote host %s not listening on port" 1268 " %s, cannot migrate" % (target, port)) 1269 1270 args.extend(["-p", "%d" % port]) 1271 if live: 1272 args.append("-l") 1273 1274 elif cmd == constants.XEN_CMD_XL: 1275 # Rather than using SSH, use socat as Ganeti cannot guarantee the presence 1276 # of usable SSH keys as of 2.13 1277 args.extend([ 1278 "-s", constants.XL_SOCAT_CMD % (target, port), 1279 "-C", self._ConfigFileName(instance_name), 1280 ]) 1281 1282 else: 1283 raise errors.HypervisorError("Unsupported Xen command: %s" % cmd) 1284 1285 args.extend([instance_name, target]) 1286 1287 result = self._RunXen(args, hvparams) 1288 if result.failed: 1289 raise errors.HypervisorError("Failed to migrate instance %s: %s" % 1290 (instance_name, result.output))
1291
1292 - def FinalizeMigrationSource(self, instance, success, live):
1293 """Finalize the instance migration on the source node. 1294 1295 @type instance: L{objects.Instance} 1296 @param instance: the instance that was migrated 1297 @type success: bool 1298 @param success: whether the migration succeeded or not 1299 @type live: bool 1300 @param live: whether the user requested a live migration or not 1301 1302 """ 1303 # pylint: disable=W0613 1304 if success: 1305 # remove old xen file after migration succeeded 1306 try: 1307 self._RemoveConfigFile(instance.name) 1308 except EnvironmentError: 1309 logging.exception("Failure while removing instance config file")
1310
1311 - def GetMigrationStatus(self, instance):
1312 """Get the migration status 1313 1314 As MigrateInstance for Xen is still blocking, if this method is called it 1315 means that MigrateInstance has completed successfully. So we can safely 1316 assume that the migration was successful and notify this fact to the client. 1317 1318 @type instance: L{objects.Instance} 1319 @param instance: the instance that is being migrated 1320 @rtype: L{objects.MigrationStatus} 1321 @return: the status of the current migration (one of 1322 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 1323 progress info that can be retrieved from the hypervisor 1324 1325 """ 1326 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
1327
1328 - def PowercycleNode(self, hvparams=None):
1329 """Xen-specific powercycle. 1330 1331 This first does a Linux reboot (which triggers automatically a Xen 1332 reboot), and if that fails it tries to do a Xen reboot. The reason 1333 we don't try a Xen reboot first is that the xen reboot launches an 1334 external command which connects to the Xen hypervisor, and that 1335 won't work in case the root filesystem is broken and/or the xend 1336 daemon is not working. 1337 1338 @type hvparams: dict of strings 1339 @param hvparams: hypervisor params to be used on this node 1340 1341 """ 1342 try: 1343 self.LinuxPowercycle() 1344 finally: 1345 xen_cmd = self._GetCommand(hvparams) 1346 utils.RunCmd([xen_cmd, "debug", "R"])
1347
1348 - def _CheckToolstack(self, xen_cmd):
1349 """Check whether the given toolstack is available on the node. 1350 1351 @type xen_cmd: string 1352 @param xen_cmd: xen command (e.g. 'xm' or 'xl') 1353 1354 """ 1355 binary_found = self._CheckToolstackBinary(xen_cmd) 1356 if not binary_found: 1357 raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd) 1358 elif xen_cmd == constants.XEN_CMD_XL: 1359 if not self._CheckToolstackXlConfigured(): 1360 raise errors.HypervisorError("Toolstack '%s' is not enabled on this" 1361 "node." % xen_cmd)
1362
1363 - def _CheckToolstackBinary(self, xen_cmd):
1364 """Checks whether the xen command's binary is found on the machine. 1365 1366 """ 1367 if xen_cmd not in constants.KNOWN_XEN_COMMANDS: 1368 raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd) 1369 result = self._run_cmd_fn(["which", xen_cmd]) 1370 return not result.failed
1371
1372 - def _CheckToolstackXlConfigured(self):
1373 """Checks whether xl is enabled on an xl-capable node. 1374 1375 @rtype: bool 1376 @returns: C{True} if 'xl' is enabled, C{False} otherwise 1377 1378 """ 1379 result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"]) 1380 if not result.failed: 1381 return True 1382 elif result.failed: 1383 if "toolstack" in result.stderr: 1384 return False 1385 # xl fails for some other reason than the toolstack 1386 else: 1387 raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s." 1388 % (constants.XEN_CMD_XL, result.stderr))
1389
1390 1391 -def WriteXenConfigEvents(config, hvp):
1392 config.write("on_poweroff = 'preserve'\n") 1393 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED: 1394 config.write("on_reboot = 'restart'\n") 1395 else: 1396 config.write("on_reboot = 'destroy'\n") 1397 config.write("on_crash = 'restart'\n")
1398
1399 1400 -class XenPvmHypervisor(XenHypervisor):
1401 """Xen PVM hypervisor interface""" 1402 1403 PARAMETERS = { 1404 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK, 1405 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK, 1406 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK, 1407 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 1408 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK, 1409 constants.HV_ROOT_PATH: hv_base.NO_CHECK, 1410 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK, 1411 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK, 1412 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 1413 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar). 1414 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK, 1415 constants.HV_REBOOT_BEHAVIOR: 1416 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS), 1417 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK, 1418 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK, 1419 constants.HV_CPU_WEIGHT: 1420 (False, lambda x: 0 < x < 65536, "invalid weight", None, None), 1421 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK, 1422 constants.HV_XEN_CMD: 1423 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS), 1424 constants.HV_XEN_CPUID: hv_base.NO_CHECK, 1425 constants.HV_SOUNDHW: hv_base.NO_CHECK, 1426 } 1427
1428 - def _GetConfig(self, instance, startup_memory, block_devices):
1429 """Write the Xen config file for the instance. 1430 1431 """ 1432 hvp = instance.hvparams 1433 config = StringIO() 1434 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n") 1435 1436 # if bootloader is True, use bootloader instead of kernel and ramdisk 1437 # parameters. 1438 if hvp[constants.HV_USE_BOOTLOADER]: 1439 # bootloader handling 1440 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH] 1441 if bootloader_path: 1442 config.write("bootloader = '%s'\n" % bootloader_path) 1443 else: 1444 raise errors.HypervisorError("Bootloader enabled, but missing" 1445 " bootloader path") 1446 1447 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS] 1448 if bootloader_args: 1449 config.write("bootargs = '%s'\n" % bootloader_args) 1450 else: 1451 # kernel handling 1452 kpath = hvp[constants.HV_KERNEL_PATH] 1453 config.write("kernel = '%s'\n" % kpath) 1454 1455 # initrd handling 1456 initrd_path = hvp[constants.HV_INITRD_PATH] 1457 if initrd_path: 1458 config.write("ramdisk = '%s'\n" % initrd_path) 1459 1460 # rest of the settings 1461 config.write("memory = %d\n" % startup_memory) 1462 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM]) 1463 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 1464 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK]) 1465 if cpu_pinning: 1466 config.write("%s\n" % cpu_pinning) 1467 cpu_cap = hvp[constants.HV_CPU_CAP] 1468 if cpu_cap: 1469 config.write("cpu_cap=%d\n" % cpu_cap) 1470 cpu_weight = hvp[constants.HV_CPU_WEIGHT] 1471 if cpu_weight: 1472 config.write("cpu_weight=%d\n" % cpu_weight) 1473 1474 config.write("name = '%s'\n" % instance.name) 1475 1476 self._WriteNicConfig(config, instance, hvp) 1477 1478 disk_data = \ 1479 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX]) 1480 config.write("disk = [%s]\n" % ",".join(disk_data)) 1481 1482 if hvp[constants.HV_ROOT_PATH]: 1483 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH]) 1484 1485 WriteXenConfigEvents(config, hvp) 1486 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS]) 1487 1488 cpuid = hvp[constants.HV_XEN_CPUID] 1489 if cpuid: 1490 config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid)) 1491 1492 if hvp[constants.HV_SOUNDHW]: 1493 config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW]) 1494 1495 return config.getvalue()
1496
1497 1498 -class XenHvmHypervisor(XenHypervisor):
1499 """Xen HVM hypervisor interface""" 1500 1501 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [ 1502 pathutils.VNC_PASSWORD_FILE, 1503 ] 1504 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [ 1505 pathutils.VNC_PASSWORD_FILE, 1506 ] 1507 1508 PARAMETERS = { 1509 constants.HV_ACPI: hv_base.NO_CHECK, 1510 constants.HV_BOOT_ORDER: (True, ) + 1511 (lambda x: x and len(x.strip("acdn")) == 0, 1512 "Invalid boot order specified, must be one or more of [acdn]", 1513 None, None), 1514 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK, 1515 constants.HV_DISK_TYPE: 1516 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES), 1517 constants.HV_NIC_TYPE: 1518 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES), 1519 constants.HV_PAE: hv_base.NO_CHECK, 1520 constants.HV_VNC_BIND_ADDRESS: 1521 (False, netutils.IP4Address.IsValid, 1522 "VNC bind address is not a valid IP address", None, None), 1523 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 1524 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK, 1525 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK, 1526 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK, 1527 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 1528 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK, 1529 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar). 1530 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK, 1531 # Add PCI passthrough 1532 constants.HV_PASSTHROUGH: hv_base.NO_CHECK, 1533 constants.HV_REBOOT_BEHAVIOR: 1534 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS), 1535 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK, 1536 constants.HV_CPU_CAP: hv_base.NO_CHECK, 1537 constants.HV_CPU_WEIGHT: 1538 (False, lambda x: 0 < x < 65535, "invalid weight", None, None), 1539 constants.HV_VIF_TYPE: 1540 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES), 1541 constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK, 1542 constants.HV_VIRIDIAN: hv_base.NO_CHECK, 1543 constants.HV_XEN_CMD: 1544 hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS), 1545 constants.HV_XEN_CPUID: hv_base.NO_CHECK, 1546 constants.HV_SOUNDHW: hv_base.NO_CHECK, 1547 } 1548
1549 - def _GetConfig(self, instance, startup_memory, block_devices):
1550 """Create a Xen 3.1 HVM config file. 1551 1552 """ 1553 hvp = instance.hvparams 1554 1555 config = StringIO() 1556 1557 # kernel handling 1558 kpath = hvp[constants.HV_KERNEL_PATH] 1559 config.write("kernel = '%s'\n" % kpath) 1560 1561 config.write("builder = 'hvm'\n") 1562 config.write("memory = %d\n" % startup_memory) 1563 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM]) 1564 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 1565 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK]) 1566 if cpu_pinning: 1567 config.write("%s\n" % cpu_pinning) 1568 cpu_cap = hvp[constants.HV_CPU_CAP] 1569 if cpu_cap: 1570 config.write("cpu_cap=%d\n" % cpu_cap) 1571 cpu_weight = hvp[constants.HV_CPU_WEIGHT] 1572 if cpu_weight: 1573 config.write("cpu_weight=%d\n" % cpu_weight) 1574 1575 config.write("name = '%s'\n" % instance.name) 1576 if hvp[constants.HV_PAE]: 1577 config.write("pae = 1\n") 1578 else: 1579 config.write("pae = 0\n") 1580 if hvp[constants.HV_ACPI]: 1581 config.write("acpi = 1\n") 1582 else: 1583 config.write("acpi = 0\n") 1584 if hvp[constants.HV_VIRIDIAN]: 1585 config.write("viridian = 1\n") 1586 else: 1587 config.write("viridian = 0\n") 1588 1589 config.write("apic = 1\n") 1590 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL]) 1591 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER]) 1592 config.write("sdl = 0\n") 1593 config.write("usb = 1\n") 1594 config.write("usbdevice = 'tablet'\n") 1595 config.write("vnc = 1\n") 1596 if hvp[constants.HV_VNC_BIND_ADDRESS] is None: 1597 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS) 1598 else: 1599 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS]) 1600 1601 if instance.network_port > constants.VNC_BASE_PORT: 1602 display = instance.network_port - constants.VNC_BASE_PORT 1603 config.write("vncdisplay = %s\n" % display) 1604 config.write("vncunused = 0\n") 1605 else: 1606 config.write("# vncdisplay = 1\n") 1607 config.write("vncunused = 1\n") 1608 1609 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE] 1610 try: 1611 password = utils.ReadFile(vnc_pwd_file) 1612 except EnvironmentError, err: 1613 raise errors.HypervisorError("Failed to open VNC password file %s: %s" % 1614 (vnc_pwd_file, err)) 1615 1616 config.write("vncpasswd = '%s'\n" % password.rstrip()) 1617 1618 config.write("serial = 'pty'\n") 1619 if hvp[constants.HV_USE_LOCALTIME]: 1620 config.write("localtime = 1\n") 1621 1622 self._WriteNicConfig(config, instance, hvp) 1623 1624 disk_data = \ 1625 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX]) 1626 1627 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH] 1628 if iso_path: 1629 iso = "'file:%s,hdc:cdrom,r'" % iso_path 1630 disk_data.append(iso) 1631 1632 config.write("disk = [%s]\n" % (",".join(disk_data))) 1633 # Add PCI passthrough 1634 pci_pass_arr = [] 1635 pci_pass = hvp[constants.HV_PASSTHROUGH] 1636 if pci_pass: 1637 pci_pass_arr = pci_pass.split(";") 1638 config.write("pci = %s\n" % pci_pass_arr) 1639 1640 WriteXenConfigEvents(config, hvp) 1641 1642 cpuid = hvp[constants.HV_XEN_CPUID] 1643 if cpuid: 1644 config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid)) 1645 1646 if hvp[constants.HV_SOUNDHW]: 1647 config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW]) 1648 1649 return config.getvalue()
1650