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  # 
   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. 
  10  # 
  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. 
  15  # 
  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 
  19  # 02110-1301, USA. 
  20   
  21   
  22  """Xen hypervisors 
  23   
  24  """ 
  25   
  26  import logging 
  27  import string # pylint: disable=W0402 
  28  from cStringIO import StringIO 
  29   
  30  from ganeti import constants 
  31  from ganeti import errors 
  32  from ganeti import utils 
  33  from ganeti.hypervisor import hv_base 
  34  from ganeti import netutils 
  35  from ganeti import objects 
  36  from ganeti import pathutils 
  37  from ganeti import ssconf 
  38   
  39   
  40  XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp") 
  41  XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf") 
  42  VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR, 
  43                                     "scripts/vif-bridge") 
  44  _DOM0_NAME = "Domain-0" 
  45  _DISK_LETTERS = string.ascii_lowercase 
  46   
  47  _FILE_DRIVER_MAP = { 
  48    constants.FD_LOOP: "file", 
  49    constants.FD_BLKTAP: "tap:aio", 
  50    constants.FD_BLKTAP2: "tap2:tapdisk:aio", 
  51    } 
52 53 54 -def _CreateConfigCpus(cpu_mask):
55 """Create a CPU config string for Xen's config file. 56 57 """ 58 # Convert the string CPU mask to a list of list of int's 59 cpu_list = utils.ParseMultiCpuMask(cpu_mask) 60 61 if len(cpu_list) == 1: 62 all_cpu_mapping = cpu_list[0] 63 if all_cpu_mapping == constants.CPU_PINNING_OFF: 64 # If CPU pinning has 1 entry that's "all", then remove the 65 # parameter from the config file 66 return None 67 else: 68 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire 69 # VM) to one physical CPU, using format 'cpu = "C"' 70 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping)) 71 else: 72 73 def _GetCPUMap(vcpu): 74 if vcpu[0] == constants.CPU_PINNING_ALL_VAL: 75 cpu_map = constants.CPU_PINNING_ALL_XEN 76 else: 77 cpu_map = ",".join(map(str, vcpu)) 78 return "\"%s\"" % cpu_map
79 80 # build the result string in format 'cpus = [ "c", "c", "c" ]', 81 # where each c is a physical CPU number, a range, a list, or any 82 # combination 83 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list)) 84
85 86 -def _RunXmList(fn, xmllist_errors):
87 """Helper function for L{_GetXmList} to run "xm list". 88 89 @type fn: callable 90 @param fn: Function returning result of running C{xm list} 91 @type xmllist_errors: list 92 @param xmllist_errors: Error list 93 @rtype: list 94 95 """ 96 result = fn() 97 if result.failed: 98 logging.error("xm list failed (%s): %s", result.fail_reason, 99 result.output) 100 xmllist_errors.append(result) 101 raise utils.RetryAgain() 102 103 # skip over the heading 104 return result.stdout.splitlines()
105
106 107 -def _ParseXmList(lines, include_node):
108 """Parses the output of C{xm list}. 109 110 @type lines: list 111 @param lines: Output lines of C{xm list} 112 @type include_node: boolean 113 @param include_node: If True, return information for Dom0 114 @return: list of tuple containing (name, id, memory, vcpus, state, time 115 spent) 116 117 """ 118 result = [] 119 120 # Iterate through all lines while ignoring header 121 for line in lines[1:]: 122 # The format of lines is: 123 # Name ID Mem(MiB) VCPUs State Time(s) 124 # Domain-0 0 3418 4 r----- 266.2 125 data = line.split() 126 if len(data) != 6: 127 raise errors.HypervisorError("Can't parse output of xm list," 128 " line: %s" % line) 129 try: 130 data[1] = int(data[1]) 131 data[2] = int(data[2]) 132 data[3] = int(data[3]) 133 data[5] = float(data[5]) 134 except (TypeError, ValueError), err: 135 raise errors.HypervisorError("Can't parse output of xm list," 136 " line: %s, error: %s" % (line, err)) 137 138 # skip the Domain-0 (optional) 139 if include_node or data[0] != _DOM0_NAME: 140 result.append(data) 141 142 return result
143
144 145 -def _GetXmList(fn, include_node, _timeout=5):
146 """Return the list of running instances. 147 148 See L{_RunXmList} and L{_ParseXmList} for parameter details. 149 150 """ 151 xmllist_errors = [] 152 try: 153 lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout, 154 args=(fn, xmllist_errors)) 155 except utils.RetryTimeout: 156 if xmllist_errors: 157 xmlist_result = xmllist_errors.pop() 158 159 errmsg = ("xm list failed, timeout exceeded (%s): %s" % 160 (xmlist_result.fail_reason, xmlist_result.output)) 161 else: 162 errmsg = "xm list failed" 163 164 raise errors.HypervisorError(errmsg) 165 166 return _ParseXmList(lines, include_node)
167
168 169 -def _IsInstanceRunning(instance_info):
170 return instance_info == "r-----" \ 171 or instance_info == "-b----"
172
173 174 -def _IsInstanceShutdown(instance_info):
175 return instance_info == "---s--"
176
177 178 -def _ParseNodeInfo(info):
179 """Return information about the node. 180 181 @return: a dict with the following keys (memory values in MiB): 182 - memory_total: the total memory size on the node 183 - memory_free: the available memory on the node for instances 184 - nr_cpus: total number of CPUs 185 - nr_nodes: in a NUMA system, the number of domains 186 - nr_sockets: the number of physical CPU sockets in the node 187 - hv_version: the hypervisor version in the form (major, minor) 188 189 """ 190 result = {} 191 cores_per_socket = threads_per_core = nr_cpus = None 192 xen_major, xen_minor = None, None 193 memory_total = None 194 memory_free = None 195 196 for line in info.splitlines(): 197 fields = line.split(":", 1) 198 199 if len(fields) < 2: 200 continue 201 202 (key, val) = map(lambda s: s.strip(), fields) 203 204 # Note: in Xen 3, memory has changed to total_memory 205 if key in ("memory", "total_memory"): 206 memory_total = int(val) 207 elif key == "free_memory": 208 memory_free = int(val) 209 elif key == "nr_cpus": 210 nr_cpus = result["cpu_total"] = int(val) 211 elif key == "nr_nodes": 212 result["cpu_nodes"] = int(val) 213 elif key == "cores_per_socket": 214 cores_per_socket = int(val) 215 elif key == "threads_per_core": 216 threads_per_core = int(val) 217 elif key == "xen_major": 218 xen_major = int(val) 219 elif key == "xen_minor": 220 xen_minor = int(val) 221 222 if None not in [cores_per_socket, threads_per_core, nr_cpus]: 223 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core) 224 225 if memory_free is not None: 226 result["memory_free"] = memory_free 227 228 if memory_total is not None: 229 result["memory_total"] = memory_total 230 231 if not (xen_major is None or xen_minor is None): 232 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor) 233 234 return result
235
236 237 -def _MergeInstanceInfo(info, fn):
238 """Updates node information from L{_ParseNodeInfo} with instance info. 239 240 @type info: dict 241 @param info: Result from L{_ParseNodeInfo} 242 @type fn: callable 243 @param fn: Function returning result of running C{xm list} 244 @rtype: dict 245 246 """ 247 total_instmem = 0 248 249 for (name, _, mem, vcpus, _, _) in fn(True): 250 if name == _DOM0_NAME: 251 info["memory_dom0"] = mem 252 info["dom0_cpus"] = vcpus 253 254 # Include Dom0 in total memory usage 255 total_instmem += mem 256 257 memory_free = info.get("memory_free") 258 memory_total = info.get("memory_total") 259 260 # Calculate memory used by hypervisor 261 if None not in [memory_total, memory_free, total_instmem]: 262 info["memory_hv"] = memory_total - memory_free - total_instmem 263 264 return info
265
266 267 -def _GetNodeInfo(info, fn):
268 """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}. 269 270 """ 271 return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
272
273 274 -def _GetConfigFileDiskData(block_devices, blockdev_prefix, 275 _letters=_DISK_LETTERS):
276 """Get disk directives for Xen config file. 277 278 This method builds the xen config disk directive according to the 279 given disk_template and block_devices. 280 281 @param block_devices: list of tuples (cfdev, rldev): 282 - cfdev: dict containing ganeti config disk part 283 - rldev: ganeti.bdev.BlockDev object 284 @param blockdev_prefix: a string containing blockdevice prefix, 285 e.g. "sd" for /dev/sda 286 287 @return: string containing disk directive for xen instance config file 288 289 """ 290 if len(block_devices) > len(_letters): 291 raise errors.HypervisorError("Too many disks") 292 293 disk_data = [] 294 295 for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices): 296 sd_name = blockdev_prefix + sd_suffix 297 298 if cfdev.mode == constants.DISK_RDWR: 299 mode = "w" 300 else: 301 mode = "r" 302 303 if cfdev.dev_type == constants.LD_FILE: 304 driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]] 305 else: 306 driver = "phy" 307 308 disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode)) 309 310 return disk_data
311
312 313 -class XenHypervisor(hv_base.BaseHypervisor):
314 """Xen generic hypervisor interface 315 316 This is the Xen base class used for both Xen PVM and HVM. It contains 317 all the functionality that is identical for both. 318 319 """ 320 CAN_MIGRATE = True 321 REBOOT_RETRY_COUNT = 60 322 REBOOT_RETRY_INTERVAL = 10 323 324 ANCILLARY_FILES = [ 325 XEND_CONFIG_FILE, 326 XL_CONFIG_FILE, 327 VIF_BRIDGE_SCRIPT, 328 ] 329 ANCILLARY_FILES_OPT = [ 330 XL_CONFIG_FILE, 331 ] 332
333 - def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
334 hv_base.BaseHypervisor.__init__(self) 335 336 if _cfgdir is None: 337 self._cfgdir = pathutils.XEN_CONFIG_DIR 338 else: 339 self._cfgdir = _cfgdir 340 341 if _run_cmd_fn is None: 342 self._run_cmd_fn = utils.RunCmd 343 else: 344 self._run_cmd_fn = _run_cmd_fn 345 346 self._cmd = _cmd
347
348 - def _GetCommand(self):
349 """Returns Xen command to use. 350 351 """ 352 if self._cmd is None: 353 # TODO: Make command a hypervisor parameter 354 cmd = constants.XEN_CMD 355 else: 356 cmd = self._cmd 357 358 if cmd not in constants.KNOWN_XEN_COMMANDS: 359 raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd) 360 361 return cmd
362
363 - def _RunXen(self, args, timeout=None):
364 """Wrapper around L{utils.process.RunCmd} to run Xen command. 365 366 If a timeout (in seconds) is specified, the command will be terminated after 367 that number of seconds. 368 369 @see: L{utils.process.RunCmd} 370 371 """ 372 cmd = [] 373 374 if timeout is not None: 375 cmd.extend(["timeout", str(timeout)]) 376 377 cmd.extend([self._GetCommand()]) 378 cmd.extend(args) 379 380 return self._run_cmd_fn(cmd)
381
382 - def _ConfigFileName(self, instance_name):
383 """Get the config file name for an instance. 384 385 @param instance_name: instance name 386 @type instance_name: str 387 @return: fully qualified path to instance config file 388 @rtype: str 389 390 """ 391 return utils.PathJoin(self._cfgdir, instance_name)
392 393 @classmethod
394 - def _GetConfig(cls, instance, startup_memory, block_devices):
395 """Build Xen configuration for an instance. 396 397 """ 398 raise NotImplementedError
399
400 - def _WriteConfigFile(self, instance_name, data):
401 """Write the Xen config file for the instance. 402 403 This version of the function just writes the config file from static data. 404 405 """ 406 # just in case it exists 407 utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name)) 408 409 cfg_file = self._ConfigFileName(instance_name) 410 try: 411 utils.WriteFile(cfg_file, data=data) 412 except EnvironmentError, err: 413 raise errors.HypervisorError("Cannot write Xen instance configuration" 414 " file %s: %s" % (cfg_file, err))
415
416 - def _ReadConfigFile(self, instance_name):
417 """Returns the contents of the instance config file. 418 419 """ 420 filename = self._ConfigFileName(instance_name) 421 422 try: 423 file_content = utils.ReadFile(filename) 424 except EnvironmentError, err: 425 raise errors.HypervisorError("Failed to load Xen config file: %s" % err) 426 427 return file_content
428
429 - def _RemoveConfigFile(self, instance_name):
430 """Remove the xen configuration file. 431 432 """ 433 utils.RemoveFile(self._ConfigFileName(instance_name))
434
435 - def _StashConfigFile(self, instance_name):
436 """Move the Xen config file to the log directory and return its new path. 437 438 """ 439 old_filename = self._ConfigFileName(instance_name) 440 base = ("%s-%s" % 441 (instance_name, utils.TimestampForFilename())) 442 new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base) 443 utils.RenameFile(old_filename, new_filename) 444 return new_filename
445
446 - def _GetXmList(self, include_node):
447 """Wrapper around module level L{_GetXmList}. 448 449 """ 450 return _GetXmList(lambda: self._RunXen(["list"]), include_node)
451
452 - def ListInstances(self):
453 """Get the list of running instances. 454 455 """ 456 xm_list = self._GetXmList(False) 457 names = [info[0] for info in xm_list] 458 return names
459
460 - def GetInstanceInfo(self, instance_name):
461 """Get instance properties. 462 463 @param instance_name: the instance name 464 465 @return: tuple (name, id, memory, vcpus, stat, times) 466 467 """ 468 xm_list = self._GetXmList(instance_name == _DOM0_NAME) 469 result = None 470 for data in xm_list: 471 if data[0] == instance_name: 472 result = data 473 break 474 return result
475
476 - def GetAllInstancesInfo(self):
477 """Get properties of all instances. 478 479 @return: list of tuples (name, id, memory, vcpus, stat, times) 480 481 """ 482 xm_list = self._GetXmList(False) 483 return xm_list
484
485 - def _MakeConfigFile(self, instance, startup_memory, block_devices):
486 """Gather configuration details and write to disk. 487 488 See L{_GetConfig} for arguments. 489 490 """ 491 buf = StringIO() 492 buf.write("# Automatically generated by Ganeti. Do not edit!\n") 493 buf.write("\n") 494 buf.write(self._GetConfig(instance, startup_memory, block_devices)) 495 buf.write("\n") 496 497 self._WriteConfigFile(instance.name, buf.getvalue())
498
499 - def StartInstance(self, instance, block_devices, startup_paused):
500 """Start an instance. 501 502 """ 503 startup_memory = self._InstanceStartupMemory(instance) 504 505 self._MakeConfigFile(instance, startup_memory, block_devices) 506 507 cmd = ["create"] 508 if startup_paused: 509 cmd.append("-p") 510 cmd.append(self._ConfigFileName(instance.name)) 511 512 result = self._RunXen(cmd) 513 if result.failed: 514 # Move the Xen configuration file to the log directory to avoid 515 # leaving a stale config file behind. 516 stashed_config = self._StashConfigFile(instance.name) 517 raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved" 518 " config file to %s" % 519 (instance.name, result.fail_reason, 520 result.output, stashed_config))
521
522 - def StopInstance(self, instance, force=False, retry=False, name=None, 523 timeout=None):
524 """Stop an instance. 525 526 A soft shutdown can be interrupted. A hard shutdown tries forever. 527 528 """ 529 assert(timeout is None or force is not None) 530 531 if name is None: 532 name = instance.name 533 534 return self._StopInstance(name, force, timeout)
535
536 - def _ShutdownInstance(self, name, timeout):
537 """Shutdown an instance if the instance is running. 538 539 The '-w' flag waits for shutdown to complete which avoids the need 540 to poll in the case where we want to destroy the domain 541 immediately after shutdown. 542 543 @type name: string 544 @param name: name of the instance to stop 545 @type timeout: int or None 546 @param timeout: a timeout after which the shutdown command should be killed, 547 or None for no timeout 548 549 """ 550 instance_info = self.GetInstanceInfo(name) 551 552 if instance_info is None or _IsInstanceShutdown(instance_info[4]): 553 logging.info("Failed to shutdown instance %s, not running", name) 554 return None 555 556 return self._RunXen(["shutdown", "-w", name], timeout)
557
558 - def _DestroyInstance(self, name):
559 """Destroy an instance if the instance if the instance exists. 560 561 @type name: string 562 @param name: name of the instance to destroy 563 564 """ 565 instance_info = self.GetInstanceInfo(name) 566 567 if instance_info is None: 568 logging.info("Failed to destroy instance %s, does not exist", name) 569 return None 570 571 return self._RunXen(["destroy", name])
572
573 - def _StopInstance(self, name, force, timeout):
574 """Stop an instance. 575 576 @type name: string 577 @param name: name of the instance to destroy 578 579 @type force: boolean 580 @param force: whether to do a "hard" stop (destroy) 581 582 @type timeout: int or None 583 @param timeout: a timeout after which the shutdown command should be killed, 584 or None for no timeout 585 586 """ 587 if force: 588 result = self._DestroyInstance(name) 589 else: 590 self._ShutdownInstance(name, timeout) 591 result = self._DestroyInstance(name) 592 593 if result is not None and result.failed and \ 594 self.GetInstanceInfo(name) is not None: 595 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" % 596 (name, result.fail_reason, result.output)) 597 598 # Remove configuration file if stopping/starting instance was successful 599 self._RemoveConfigFile(name)
600
601 - def RebootInstance(self, instance):
602 """Reboot an instance. 603 604 """ 605 ini_info = self.GetInstanceInfo(instance.name) 606 607 if ini_info is None: 608 raise errors.HypervisorError("Failed to reboot instance %s," 609 " not running" % instance.name) 610 611 result = self._RunXen(["reboot", instance.name]) 612 if result.failed: 613 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" % 614 (instance.name, result.fail_reason, 615 result.output)) 616 617 def _CheckInstance(): 618 new_info = self.GetInstanceInfo(instance.name) 619 620 # check if the domain ID has changed or the run time has decreased 621 if (new_info is not None and 622 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])): 623 return 624 625 raise utils.RetryAgain()
626 627 try: 628 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL, 629 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT) 630 except utils.RetryTimeout: 631 raise errors.HypervisorError("Failed to reboot instance %s: instance" 632 " did not reboot in the expected interval" % 633 (instance.name, ))
634
635 - def BalloonInstanceMemory(self, instance, mem):
636 """Balloon an instance memory to a certain value. 637 638 @type instance: L{objects.Instance} 639 @param instance: instance to be accepted 640 @type mem: int 641 @param mem: actual memory size to use for instance runtime 642 643 """ 644 result = self._RunXen(["mem-set", instance.name, mem]) 645 if result.failed: 646 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" % 647 (instance.name, result.fail_reason, 648 result.output)) 649 650 # Update configuration file 651 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem] 652 cmd.append(self._ConfigFileName(instance.name)) 653 654 result = utils.RunCmd(cmd) 655 if result.failed: 656 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" % 657 (instance.name, result.fail_reason, 658 result.output))
659
660 - def GetNodeInfo(self):
661 """Return information about the node. 662 663 @see: L{_GetNodeInfo} and L{_ParseNodeInfo} 664 665 """ 666 result = self._RunXen(["info"]) 667 if result.failed: 668 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason, 669 result.output) 670 return None 671 672 return _GetNodeInfo(result.stdout, self._GetXmList)
673 674 @classmethod
675 - def GetInstanceConsole(cls, instance, hvparams, beparams):
676 """Return a command for connecting to the console of an instance. 677 678 """ 679 return objects.InstanceConsole(instance=instance.name, 680 kind=constants.CONS_SSH, 681 host=instance.primary_node, 682 user=constants.SSH_CONSOLE_USER, 683 command=[pathutils.XEN_CONSOLE_WRAPPER, 684 constants.XEN_CMD, instance.name])
685
686 - def Verify(self):
687 """Verify the hypervisor. 688 689 For Xen, this verifies that the xend process is running. 690 691 @return: Problem description if something is wrong, C{None} otherwise 692 693 """ 694 result = self._RunXen(["info"]) 695 if result.failed: 696 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output) 697 698 return None
699
700 - def MigrationInfo(self, instance):
701 """Get instance information to perform a migration. 702 703 @type instance: L{objects.Instance} 704 @param instance: instance to be migrated 705 @rtype: string 706 @return: content of the xen config file 707 708 """ 709 return self._ReadConfigFile(instance.name)
710
711 - def AcceptInstance(self, instance, info, target):
712 """Prepare to accept an instance. 713 714 @type instance: L{objects.Instance} 715 @param instance: instance to be accepted 716 @type info: string 717 @param info: content of the xen config file on the source node 718 @type target: string 719 @param target: target host (usually ip), on this node 720 721 """ 722 pass
723
724 - def FinalizeMigrationDst(self, instance, info, success):
725 """Finalize an instance migration. 726 727 After a successful migration we write the xen config file. 728 We do nothing on a failure, as we did not change anything at accept time. 729 730 @type instance: L{objects.Instance} 731 @param instance: instance whose migration is being finalized 732 @type info: string 733 @param info: content of the xen config file on the source node 734 @type success: boolean 735 @param success: whether the migration was a success or a failure 736 737 """ 738 if success: 739 self._WriteConfigFile(instance.name, info)
740
741 - def MigrateInstance(self, instance, target, live):
742 """Migrate an instance to a target node. 743 744 The migration will not be attempted if the instance is not 745 currently running. 746 747 @type instance: L{objects.Instance} 748 @param instance: the instance to be migrated 749 @type target: string 750 @param target: ip address of the target node 751 @type live: boolean 752 @param live: perform a live migration 753 754 """ 755 port = instance.hvparams[constants.HV_MIGRATION_PORT] 756 757 # TODO: Pass cluster name via RPC 758 cluster_name = ssconf.SimpleStore().GetClusterName() 759 760 return self._MigrateInstance(cluster_name, instance.name, target, port, 761 live)
762
763 - def _MigrateInstance(self, cluster_name, instance_name, target, port, live, 764 _ping_fn=netutils.TcpPing):
765 """Migrate an instance to a target node. 766 767 @see: L{MigrateInstance} for details 768 769 """ 770 if self.GetInstanceInfo(instance_name) is None: 771 raise errors.HypervisorError("Instance not running, cannot migrate") 772 773 cmd = self._GetCommand() 774 775 if (cmd == constants.XEN_CMD_XM and 776 not _ping_fn(target, port, live_port_needed=True)): 777 raise errors.HypervisorError("Remote host %s not listening on port" 778 " %s, cannot migrate" % (target, port)) 779 780 args = ["migrate"] 781 782 if cmd == constants.XEN_CMD_XM: 783 args.extend(["-p", "%d" % port]) 784 if live: 785 args.append("-l") 786 787 elif cmd == constants.XEN_CMD_XL: 788 args.extend([ 789 "-s", constants.XL_SSH_CMD % cluster_name, 790 "-C", self._ConfigFileName(instance_name), 791 ]) 792 793 else: 794 raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd) 795 796 args.extend([instance_name, target]) 797 798 result = self._RunXen(args) 799 if result.failed: 800 raise errors.HypervisorError("Failed to migrate instance %s: %s" % 801 (instance_name, result.output))
802
803 - def FinalizeMigrationSource(self, instance, success, live):
804 """Finalize the instance migration on the source node. 805 806 @type instance: L{objects.Instance} 807 @param instance: the instance that was migrated 808 @type success: bool 809 @param success: whether the migration succeeded or not 810 @type live: bool 811 @param live: whether the user requested a live migration or not 812 813 """ 814 # pylint: disable=W0613 815 if success: 816 # remove old xen file after migration succeeded 817 try: 818 self._RemoveConfigFile(instance.name) 819 except EnvironmentError: 820 logging.exception("Failure while removing instance config file")
821
822 - def GetMigrationStatus(self, instance):
823 """Get the migration status 824 825 As MigrateInstance for Xen is still blocking, if this method is called it 826 means that MigrateInstance has completed successfully. So we can safely 827 assume that the migration was successful and notify this fact to the client. 828 829 @type instance: L{objects.Instance} 830 @param instance: the instance that is being migrated 831 @rtype: L{objects.MigrationStatus} 832 @return: the status of the current migration (one of 833 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 834 progress info that can be retrieved from the hypervisor 835 836 """ 837 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
838 839 @classmethod
840 - def PowercycleNode(cls):
841 """Xen-specific powercycle. 842 843 This first does a Linux reboot (which triggers automatically a Xen 844 reboot), and if that fails it tries to do a Xen reboot. The reason 845 we don't try a Xen reboot first is that the xen reboot launches an 846 external command which connects to the Xen hypervisor, and that 847 won't work in case the root filesystem is broken and/or the xend 848 daemon is not working. 849 850 """ 851 try: 852 cls.LinuxPowercycle() 853 finally: 854 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
855
856 857 -class XenPvmHypervisor(XenHypervisor):
858 """Xen PVM hypervisor interface""" 859 860 PARAMETERS = { 861 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK, 862 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK, 863 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK, 864 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 865 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK, 866 constants.HV_ROOT_PATH: hv_base.NO_CHECK, 867 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK, 868 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK, 869 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 870 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar). 871 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK, 872 constants.HV_REBOOT_BEHAVIOR: 873 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS), 874 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK, 875 constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK, 876 constants.HV_CPU_WEIGHT: 877 (False, lambda x: 0 < x < 65536, "invalid weight", None, None), 878 } 879
880 - def _GetConfig(self, instance, startup_memory, block_devices):
881 """Write the Xen config file for the instance. 882 883 """ 884 hvp = instance.hvparams 885 config = StringIO() 886 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n") 887 888 # if bootloader is True, use bootloader instead of kernel and ramdisk 889 # parameters. 890 if hvp[constants.HV_USE_BOOTLOADER]: 891 # bootloader handling 892 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH] 893 if bootloader_path: 894 config.write("bootloader = '%s'\n" % bootloader_path) 895 else: 896 raise errors.HypervisorError("Bootloader enabled, but missing" 897 " bootloader path") 898 899 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS] 900 if bootloader_args: 901 config.write("bootargs = '%s'\n" % bootloader_args) 902 else: 903 # kernel handling 904 kpath = hvp[constants.HV_KERNEL_PATH] 905 config.write("kernel = '%s'\n" % kpath) 906 907 # initrd handling 908 initrd_path = hvp[constants.HV_INITRD_PATH] 909 if initrd_path: 910 config.write("ramdisk = '%s'\n" % initrd_path) 911 912 # rest of the settings 913 config.write("memory = %d\n" % startup_memory) 914 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM]) 915 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 916 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK]) 917 if cpu_pinning: 918 config.write("%s\n" % cpu_pinning) 919 cpu_cap = hvp[constants.HV_CPU_CAP] 920 if cpu_cap: 921 config.write("cpu_cap=%d\n" % cpu_cap) 922 cpu_weight = hvp[constants.HV_CPU_WEIGHT] 923 if cpu_weight: 924 config.write("cpu_weight=%d\n" % cpu_weight) 925 926 config.write("name = '%s'\n" % instance.name) 927 928 vif_data = [] 929 for nic in instance.nics: 930 nic_str = "mac=%s" % (nic.mac) 931 ip = getattr(nic, "ip", None) 932 if ip is not None: 933 nic_str += ", ip=%s" % ip 934 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 935 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK] 936 vif_data.append("'%s'" % nic_str) 937 938 disk_data = \ 939 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX]) 940 941 config.write("vif = [%s]\n" % ",".join(vif_data)) 942 config.write("disk = [%s]\n" % ",".join(disk_data)) 943 944 if hvp[constants.HV_ROOT_PATH]: 945 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH]) 946 config.write("on_poweroff = 'destroy'\n") 947 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED: 948 config.write("on_reboot = 'restart'\n") 949 else: 950 config.write("on_reboot = 'destroy'\n") 951 config.write("on_crash = 'restart'\n") 952 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS]) 953 954 return config.getvalue()
955
956 957 -class XenHvmHypervisor(XenHypervisor):
958 """Xen HVM hypervisor interface""" 959 960 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [ 961 pathutils.VNC_PASSWORD_FILE, 962 ] 963 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [ 964 pathutils.VNC_PASSWORD_FILE, 965 ] 966 967 PARAMETERS = { 968 constants.HV_ACPI: hv_base.NO_CHECK, 969 constants.HV_BOOT_ORDER: (True, ) + 970 (lambda x: x and len(x.strip("acdn")) == 0, 971 "Invalid boot order specified, must be one or more of [acdn]", 972 None, None), 973 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK, 974 constants.HV_DISK_TYPE: 975 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES), 976 constants.HV_NIC_TYPE: 977 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES), 978 constants.HV_PAE: hv_base.NO_CHECK, 979 constants.HV_VNC_BIND_ADDRESS: 980 (False, netutils.IP4Address.IsValid, 981 "VNC bind address is not a valid IP address", None, None), 982 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 983 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK, 984 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK, 985 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK, 986 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 987 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK, 988 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar). 989 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK, 990 # Add PCI passthrough 991 constants.HV_PASSTHROUGH: hv_base.NO_CHECK, 992 constants.HV_REBOOT_BEHAVIOR: 993 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS), 994 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK, 995 constants.HV_CPU_CAP: hv_base.NO_CHECK, 996 constants.HV_CPU_WEIGHT: 997 (False, lambda x: 0 < x < 65535, "invalid weight", None, None), 998 constants.HV_VIF_TYPE: 999 hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES), 1000 constants.HV_VIRIDIAN: hv_base.NO_CHECK, 1001 } 1002
1003 - def _GetConfig(self, instance, startup_memory, block_devices):
1004 """Create a Xen 3.1 HVM config file. 1005 1006 """ 1007 hvp = instance.hvparams 1008 1009 config = StringIO() 1010 1011 # kernel handling 1012 kpath = hvp[constants.HV_KERNEL_PATH] 1013 config.write("kernel = '%s'\n" % kpath) 1014 1015 config.write("builder = 'hvm'\n") 1016 config.write("memory = %d\n" % startup_memory) 1017 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM]) 1018 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 1019 cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK]) 1020 if cpu_pinning: 1021 config.write("%s\n" % cpu_pinning) 1022 cpu_cap = hvp[constants.HV_CPU_CAP] 1023 if cpu_cap: 1024 config.write("cpu_cap=%d\n" % cpu_cap) 1025 cpu_weight = hvp[constants.HV_CPU_WEIGHT] 1026 if cpu_weight: 1027 config.write("cpu_weight=%d\n" % cpu_weight) 1028 1029 config.write("name = '%s'\n" % instance.name) 1030 if hvp[constants.HV_PAE]: 1031 config.write("pae = 1\n") 1032 else: 1033 config.write("pae = 0\n") 1034 if hvp[constants.HV_ACPI]: 1035 config.write("acpi = 1\n") 1036 else: 1037 config.write("acpi = 0\n") 1038 if hvp[constants.HV_VIRIDIAN]: 1039 config.write("viridian = 1\n") 1040 else: 1041 config.write("viridian = 0\n") 1042 1043 config.write("apic = 1\n") 1044 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL]) 1045 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER]) 1046 config.write("sdl = 0\n") 1047 config.write("usb = 1\n") 1048 config.write("usbdevice = 'tablet'\n") 1049 config.write("vnc = 1\n") 1050 if hvp[constants.HV_VNC_BIND_ADDRESS] is None: 1051 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS) 1052 else: 1053 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS]) 1054 1055 if instance.network_port > constants.VNC_BASE_PORT: 1056 display = instance.network_port - constants.VNC_BASE_PORT 1057 config.write("vncdisplay = %s\n" % display) 1058 config.write("vncunused = 0\n") 1059 else: 1060 config.write("# vncdisplay = 1\n") 1061 config.write("vncunused = 1\n") 1062 1063 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE] 1064 try: 1065 password = utils.ReadFile(vnc_pwd_file) 1066 except EnvironmentError, err: 1067 raise errors.HypervisorError("Failed to open VNC password file %s: %s" % 1068 (vnc_pwd_file, err)) 1069 1070 config.write("vncpasswd = '%s'\n" % password.rstrip()) 1071 1072 config.write("serial = 'pty'\n") 1073 if hvp[constants.HV_USE_LOCALTIME]: 1074 config.write("localtime = 1\n") 1075 1076 vif_data = [] 1077 # Note: what is called 'nic_type' here, is used as value for the xen nic 1078 # vif config parameter 'model'. For the xen nic vif parameter 'type', we use 1079 # the 'vif_type' to avoid a clash of notation. 1080 nic_type = hvp[constants.HV_NIC_TYPE] 1081 1082 if nic_type is None: 1083 vif_type_str = "" 1084 if hvp[constants.HV_VIF_TYPE]: 1085 vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE] 1086 # ensure old instances don't change 1087 nic_type_str = vif_type_str 1088 elif nic_type == constants.HT_NIC_PARAVIRTUAL: 1089 nic_type_str = ", type=paravirtualized" 1090 else: 1091 # parameter 'model' is only valid with type 'ioemu' 1092 nic_type_str = ", model=%s, type=%s" % \ 1093 (nic_type, constants.HT_HVM_VIF_IOEMU) 1094 for nic in instance.nics: 1095 nic_str = "mac=%s%s" % (nic.mac, nic_type_str) 1096 ip = getattr(nic, "ip", None) 1097 if ip is not None: 1098 nic_str += ", ip=%s" % ip 1099 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 1100 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK] 1101 vif_data.append("'%s'" % nic_str) 1102 1103 config.write("vif = [%s]\n" % ",".join(vif_data)) 1104 1105 disk_data = \ 1106 _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX]) 1107 1108 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH] 1109 if iso_path: 1110 iso = "'file:%s,hdc:cdrom,r'" % iso_path 1111 disk_data.append(iso) 1112 1113 config.write("disk = [%s]\n" % (",".join(disk_data))) 1114 # Add PCI passthrough 1115 pci_pass_arr = [] 1116 pci_pass = hvp[constants.HV_PASSTHROUGH] 1117 if pci_pass: 1118 pci_pass_arr = pci_pass.split(";") 1119 config.write("pci = %s\n" % pci_pass_arr) 1120 config.write("on_poweroff = 'destroy'\n") 1121 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED: 1122 config.write("on_reboot = 'restart'\n") 1123 else: 1124 config.write("on_reboot = 'destroy'\n") 1125 config.write("on_crash = 'restart'\n") 1126 1127 return config.getvalue()
1128