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  from cStringIO import StringIO 
 28   
 29  from ganeti import constants 
 30  from ganeti import errors 
 31  from ganeti import utils 
 32  from ganeti.hypervisor import hv_base 
 33  from ganeti import netutils 
 34  from ganeti import objects 
 35  from ganeti import ssconf 
 36   
 37   
 38  XEND_CONFIG_FILE = "/etc/xen/xend-config.sxp" 
 39  XL_CONFIG_FILE = "/etc/xen/xl.conf" 
 40  VIF_BRIDGE_SCRIPT = "/etc/xen/scripts/vif-bridge" 
 41  _DOM0_NAME = "Domain-0" 
42 43 44 -class XenHypervisor(hv_base.BaseHypervisor):
45 """Xen generic hypervisor interface 46 47 This is the Xen base class used for both Xen PVM and HVM. It contains 48 all the functionality that is identical for both. 49 50 """ 51 CAN_MIGRATE = True 52 REBOOT_RETRY_COUNT = 60 53 REBOOT_RETRY_INTERVAL = 10 54 55 ANCILLARY_FILES = [ 56 XEND_CONFIG_FILE, 57 XL_CONFIG_FILE, 58 VIF_BRIDGE_SCRIPT, 59 ] 60 ANCILLARY_FILES_OPT = [ 61 XL_CONFIG_FILE, 62 ] 63 64 @staticmethod
65 - def _ConfigFileName(instance_name):
66 """Get the config file name for an instance. 67 68 @param instance_name: instance name 69 @type instance_name: str 70 @return: fully qualified path to instance config file 71 @rtype: str 72 73 """ 74 return "/etc/xen/%s" % instance_name
75 76 @classmethod
77 - def _WriteConfigFile(cls, instance, startup_memory, block_devices):
78 """Write the Xen config file for the instance. 79 80 """ 81 raise NotImplementedError
82 83 @staticmethod
84 - def _WriteConfigFileStatic(instance_name, data):
85 """Write the Xen config file for the instance. 86 87 This version of the function just writes the config file from static data. 88 89 """ 90 # just in case it exists 91 utils.RemoveFile("/etc/xen/auto/%s" % instance_name) 92 cfg_file = XenHypervisor._ConfigFileName(instance_name) 93 try: 94 utils.WriteFile(cfg_file, data=data) 95 except EnvironmentError, err: 96 raise errors.HypervisorError("Cannot write Xen instance configuration" 97 " file %s: %s" % (cfg_file, err))
98 99 @staticmethod
100 - def _ReadConfigFile(instance_name):
101 """Returns the contents of the instance config file. 102 103 """ 104 try: 105 file_content = utils.ReadFile( 106 XenHypervisor._ConfigFileName(instance_name)) 107 except EnvironmentError, err: 108 raise errors.HypervisorError("Failed to load Xen config file: %s" % err) 109 return file_content
110 111 @staticmethod
112 - def _RemoveConfigFile(instance_name):
113 """Remove the xen configuration file. 114 115 """ 116 utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
117 118 @classmethod
119 - def _CreateConfigCpus(cls, cpu_mask):
120 """Create a CPU config string that's compatible with Xen's 121 configuration file. 122 123 """ 124 # Convert the string CPU mask to a list of list of int's 125 cpu_list = utils.ParseMultiCpuMask(cpu_mask) 126 127 if len(cpu_list) == 1: 128 all_cpu_mapping = cpu_list[0] 129 if all_cpu_mapping == constants.CPU_PINNING_OFF: 130 # If CPU pinning has 1 entry that's "all", then remove the 131 # parameter from the config file 132 return None 133 else: 134 # If CPU pinning has one non-all entry, mapping all vCPUS (the entire 135 # VM) to one physical CPU, using format 'cpu = "C"' 136 return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping)) 137 else: 138 def _GetCPUMap(vcpu): 139 if vcpu[0] == constants.CPU_PINNING_ALL_VAL: 140 cpu_map = constants.CPU_PINNING_ALL_XEN 141 else: 142 cpu_map = ",".join(map(str, vcpu)) 143 return "\"%s\"" % cpu_map
144 145 # build the result string in format 'cpus = [ "c", "c", "c" ]', 146 # where each c is a physical CPU number, a range, a list, or any 147 # combination 148 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
149 150 @staticmethod
151 - def _RunXmList(xmlist_errors):
152 """Helper function for L{_GetXMList} to run "xm list". 153 154 """ 155 result = utils.RunCmd([constants.XEN_CMD, "list"]) 156 if result.failed: 157 logging.error("xm list failed (%s): %s", result.fail_reason, 158 result.output) 159 xmlist_errors.append(result) 160 raise utils.RetryAgain() 161 162 # skip over the heading 163 return result.stdout.splitlines()[1:]
164 165 @classmethod
166 - def _GetXMList(cls, include_node):
167 """Return the list of running instances. 168 169 If the include_node argument is True, then we return information 170 for dom0 also, otherwise we filter that from the return value. 171 172 @return: list of (name, id, memory, vcpus, state, time spent) 173 174 """ 175 xmlist_errors = [] 176 try: 177 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, )) 178 except utils.RetryTimeout: 179 if xmlist_errors: 180 xmlist_result = xmlist_errors.pop() 181 182 errmsg = ("xm list failed, timeout exceeded (%s): %s" % 183 (xmlist_result.fail_reason, xmlist_result.output)) 184 else: 185 errmsg = "xm list failed" 186 187 raise errors.HypervisorError(errmsg) 188 189 result = [] 190 for line in lines: 191 # The format of lines is: 192 # Name ID Mem(MiB) VCPUs State Time(s) 193 # Domain-0 0 3418 4 r----- 266.2 194 data = line.split() 195 if len(data) != 6: 196 raise errors.HypervisorError("Can't parse output of xm list," 197 " line: %s" % line) 198 try: 199 data[1] = int(data[1]) 200 data[2] = int(data[2]) 201 data[3] = int(data[3]) 202 data[5] = float(data[5]) 203 except (TypeError, ValueError), err: 204 raise errors.HypervisorError("Can't parse output of xm list," 205 " line: %s, error: %s" % (line, err)) 206 207 # skip the Domain-0 (optional) 208 if include_node or data[0] != _DOM0_NAME: 209 result.append(data) 210 211 return result
212
213 - def ListInstances(self):
214 """Get the list of running instances. 215 216 """ 217 xm_list = self._GetXMList(False) 218 names = [info[0] for info in xm_list] 219 return names
220
221 - def GetInstanceInfo(self, instance_name):
222 """Get instance properties. 223 224 @param instance_name: the instance name 225 226 @return: tuple (name, id, memory, vcpus, stat, times) 227 228 """ 229 xm_list = self._GetXMList(instance_name == _DOM0_NAME) 230 result = None 231 for data in xm_list: 232 if data[0] == instance_name: 233 result = data 234 break 235 return result
236
237 - def GetAllInstancesInfo(self):
238 """Get properties of all instances. 239 240 @return: list of tuples (name, id, memory, vcpus, stat, times) 241 242 """ 243 xm_list = self._GetXMList(False) 244 return xm_list
245
246 - def StartInstance(self, instance, block_devices, startup_paused):
247 """Start an instance. 248 249 """ 250 startup_memory = self._InstanceStartupMemory(instance) 251 self._WriteConfigFile(instance, startup_memory, block_devices) 252 cmd = [constants.XEN_CMD, "create"] 253 if startup_paused: 254 cmd.extend(["-p"]) 255 cmd.extend([self._ConfigFileName(instance.name)]) 256 result = utils.RunCmd(cmd) 257 258 if result.failed: 259 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" % 260 (instance.name, result.fail_reason, 261 result.output))
262
263 - def StopInstance(self, instance, force=False, retry=False, name=None):
264 """Stop an instance. 265 266 """ 267 if name is None: 268 name = instance.name 269 self._RemoveConfigFile(name) 270 if force: 271 command = [constants.XEN_CMD, "destroy", name] 272 else: 273 command = [constants.XEN_CMD, "shutdown", name] 274 result = utils.RunCmd(command) 275 276 if result.failed: 277 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" % 278 (name, result.fail_reason, result.output))
279
280 - def RebootInstance(self, instance):
281 """Reboot an instance. 282 283 """ 284 ini_info = self.GetInstanceInfo(instance.name) 285 286 if ini_info is None: 287 raise errors.HypervisorError("Failed to reboot instance %s," 288 " not running" % instance.name) 289 290 result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name]) 291 if result.failed: 292 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" % 293 (instance.name, result.fail_reason, 294 result.output)) 295 296 def _CheckInstance(): 297 new_info = self.GetInstanceInfo(instance.name) 298 299 # check if the domain ID has changed or the run time has decreased 300 if (new_info is not None and 301 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])): 302 return 303 304 raise utils.RetryAgain()
305 306 try: 307 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL, 308 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT) 309 except utils.RetryTimeout: 310 raise errors.HypervisorError("Failed to reboot instance %s: instance" 311 " did not reboot in the expected interval" % 312 (instance.name, )) 313
314 - def BalloonInstanceMemory(self, instance, mem):
315 """Balloon an instance memory to a certain value. 316 317 @type instance: L{objects.Instance} 318 @param instance: instance to be accepted 319 @type mem: int 320 @param mem: actual memory size to use for instance runtime 321 322 """ 323 cmd = [constants.XEN_CMD, "mem-set", instance.name, mem] 324 result = utils.RunCmd(cmd) 325 if result.failed: 326 raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" % 327 (instance.name, result.fail_reason, 328 result.output)) 329 cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem] 330 cmd.append(XenHypervisor._ConfigFileName(instance.name)) 331 result = utils.RunCmd(cmd) 332 if result.failed: 333 raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" % 334 (instance.name, result.fail_reason, 335 result.output))
336
337 - def GetNodeInfo(self):
338 """Return information about the node. 339 340 @return: a dict with the following keys (memory values in MiB): 341 - memory_total: the total memory size on the node 342 - memory_free: the available memory on the node for instances 343 - memory_dom0: the memory used by the node itself, if available 344 - nr_cpus: total number of CPUs 345 - nr_nodes: in a NUMA system, the number of domains 346 - nr_sockets: the number of physical CPU sockets in the node 347 - hv_version: the hypervisor version in the form (major, minor) 348 349 """ 350 result = utils.RunCmd([constants.XEN_CMD, "info"]) 351 if result.failed: 352 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason, 353 result.output) 354 return None 355 356 xmoutput = result.stdout.splitlines() 357 result = {} 358 cores_per_socket = threads_per_core = nr_cpus = None 359 xen_major, xen_minor = None, None 360 memory_total = None 361 memory_free = None 362 363 for line in xmoutput: 364 splitfields = line.split(":", 1) 365 366 if len(splitfields) > 1: 367 key = splitfields[0].strip() 368 val = splitfields[1].strip() 369 370 # note: in xen 3, memory has changed to total_memory 371 if key == "memory" or key == "total_memory": 372 memory_total = int(val) 373 elif key == "free_memory": 374 memory_free = int(val) 375 elif key == "nr_cpus": 376 nr_cpus = result["cpu_total"] = int(val) 377 elif key == "nr_nodes": 378 result["cpu_nodes"] = int(val) 379 elif key == "cores_per_socket": 380 cores_per_socket = int(val) 381 elif key == "threads_per_core": 382 threads_per_core = int(val) 383 elif key == "xen_major": 384 xen_major = int(val) 385 elif key == "xen_minor": 386 xen_minor = int(val) 387 388 if None not in [cores_per_socket, threads_per_core, nr_cpus]: 389 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core) 390 391 total_instmem = 0 392 for (name, _, mem, vcpus, _, _) in self._GetXMList(True): 393 if name == _DOM0_NAME: 394 result["memory_dom0"] = mem 395 result["dom0_cpus"] = vcpus 396 397 # Include Dom0 in total memory usage 398 total_instmem += mem 399 400 if memory_free is not None: 401 result["memory_free"] = memory_free 402 403 if memory_total is not None: 404 result["memory_total"] = memory_total 405 406 # Calculate memory used by hypervisor 407 if None not in [memory_total, memory_free, total_instmem]: 408 result["memory_hv"] = memory_total - memory_free - total_instmem 409 410 if not (xen_major is None or xen_minor is None): 411 result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor) 412 413 return result
414 415 @classmethod
416 - def GetInstanceConsole(cls, instance, hvparams, beparams):
417 """Return a command for connecting to the console of an instance. 418 419 """ 420 return objects.InstanceConsole(instance=instance.name, 421 kind=constants.CONS_SSH, 422 host=instance.primary_node, 423 user=constants.GANETI_RUNAS, 424 command=[constants.XEN_CONSOLE_WRAPPER, 425 constants.XEN_CMD, instance.name])
426
427 - def Verify(self):
428 """Verify the hypervisor. 429 430 For Xen, this verifies that the xend process is running. 431 432 """ 433 result = utils.RunCmd([constants.XEN_CMD, "info"]) 434 if result.failed: 435 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
436 437 @staticmethod
438 - def _GetConfigFileDiskData(block_devices, blockdev_prefix):
439 """Get disk directive for xen config file. 440 441 This method builds the xen config disk directive according to the 442 given disk_template and block_devices. 443 444 @param block_devices: list of tuples (cfdev, rldev): 445 - cfdev: dict containing ganeti config disk part 446 - rldev: ganeti.bdev.BlockDev object 447 @param blockdev_prefix: a string containing blockdevice prefix, 448 e.g. "sd" for /dev/sda 449 450 @return: string containing disk directive for xen instance config file 451 452 """ 453 FILE_DRIVER_MAP = { 454 constants.FD_LOOP: "file", 455 constants.FD_BLKTAP: "tap:aio", 456 } 457 disk_data = [] 458 if len(block_devices) > 24: 459 # 'z' - 'a' = 24 460 raise errors.HypervisorError("Too many disks") 461 namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)] 462 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices): 463 if cfdev.mode == constants.DISK_RDWR: 464 mode = "w" 465 else: 466 mode = "r" 467 if cfdev.dev_type == constants.LD_FILE: 468 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]], 469 dev_path, sd_name, mode) 470 else: 471 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode) 472 disk_data.append(line) 473 474 return disk_data
475
476 - def MigrationInfo(self, instance):
477 """Get instance information to perform a migration. 478 479 @type instance: L{objects.Instance} 480 @param instance: instance to be migrated 481 @rtype: string 482 @return: content of the xen config file 483 484 """ 485 return self._ReadConfigFile(instance.name)
486
487 - def AcceptInstance(self, instance, info, target):
488 """Prepare to accept an instance. 489 490 @type instance: L{objects.Instance} 491 @param instance: instance to be accepted 492 @type info: string 493 @param info: content of the xen config file on the source node 494 @type target: string 495 @param target: target host (usually ip), on this node 496 497 """ 498 pass
499
500 - def FinalizeMigrationDst(self, instance, info, success):
501 """Finalize an instance migration. 502 503 After a successful migration we write the xen config file. 504 We do nothing on a failure, as we did not change anything at accept time. 505 506 @type instance: L{objects.Instance} 507 @param instance: instance whose migration is being finalized 508 @type info: string 509 @param info: content of the xen config file on the source node 510 @type success: boolean 511 @param success: whether the migration was a success or a failure 512 513 """ 514 if success: 515 self._WriteConfigFileStatic(instance.name, info)
516
517 - def MigrateInstance(self, instance, target, live):
518 """Migrate an instance to a target node. 519 520 The migration will not be attempted if the instance is not 521 currently running. 522 523 @type instance: L{objects.Instance} 524 @param instance: the instance to be migrated 525 @type target: string 526 @param target: ip address of the target node 527 @type live: boolean 528 @param live: perform a live migration 529 530 """ 531 if self.GetInstanceInfo(instance.name) is None: 532 raise errors.HypervisorError("Instance not running, cannot migrate") 533 534 port = instance.hvparams[constants.HV_MIGRATION_PORT] 535 536 if (constants.XEN_CMD == constants.XEN_CMD_XM and 537 not netutils.TcpPing(target, port, live_port_needed=True)): 538 raise errors.HypervisorError("Remote host %s not listening on port" 539 " %s, cannot migrate" % (target, port)) 540 541 args = [constants.XEN_CMD, "migrate"] 542 if constants.XEN_CMD == constants.XEN_CMD_XM: 543 args.extend(["-p", "%d" % port]) 544 if live: 545 args.append("-l") 546 elif constants.XEN_CMD == constants.XEN_CMD_XL: 547 cluster_name = ssconf.SimpleStore().GetClusterName() 548 args.extend(["-s", constants.XL_SSH_CMD % cluster_name]) 549 args.extend(["-C", self._ConfigFileName(instance.name)]) 550 else: 551 raise errors.HypervisorError("Unsupported xen command: %s" % 552 constants.XEN_CMD) 553 554 args.extend([instance.name, target]) 555 result = utils.RunCmd(args) 556 if result.failed: 557 raise errors.HypervisorError("Failed to migrate instance %s: %s" % 558 (instance.name, result.output))
559
560 - def FinalizeMigrationSource(self, instance, success, live):
561 """Finalize the instance migration on the source node. 562 563 @type instance: L{objects.Instance} 564 @param instance: the instance that was migrated 565 @type success: bool 566 @param success: whether the migration succeeded or not 567 @type live: bool 568 @param live: whether the user requested a live migration or not 569 570 """ 571 # pylint: disable=W0613 572 if success: 573 # remove old xen file after migration succeeded 574 try: 575 self._RemoveConfigFile(instance.name) 576 except EnvironmentError: 577 logging.exception("Failure while removing instance config file")
578
579 - def GetMigrationStatus(self, instance):
580 """Get the migration status 581 582 As MigrateInstance for Xen is still blocking, if this method is called it 583 means that MigrateInstance has completed successfully. So we can safely 584 assume that the migration was successful and notify this fact to the client. 585 586 @type instance: L{objects.Instance} 587 @param instance: the instance that is being migrated 588 @rtype: L{objects.MigrationStatus} 589 @return: the status of the current migration (one of 590 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 591 progress info that can be retrieved from the hypervisor 592 593 """ 594 return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
595 596 @classmethod
597 - def PowercycleNode(cls):
598 """Xen-specific powercycle. 599 600 This first does a Linux reboot (which triggers automatically a Xen 601 reboot), and if that fails it tries to do a Xen reboot. The reason 602 we don't try a Xen reboot first is that the xen reboot launches an 603 external command which connects to the Xen hypervisor, and that 604 won't work in case the root filesystem is broken and/or the xend 605 daemon is not working. 606 607 """ 608 try: 609 cls.LinuxPowercycle() 610 finally: 611 utils.RunCmd([constants.XEN_CMD, "debug", "R"])
612
613 614 -class XenPvmHypervisor(XenHypervisor):
615 """Xen PVM hypervisor interface""" 616 617 PARAMETERS = { 618 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK, 619 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK, 620 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK, 621 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 622 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK, 623 constants.HV_ROOT_PATH: hv_base.NO_CHECK, 624 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK, 625 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK, 626 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 627 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar). 628 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK, 629 constants.HV_REBOOT_BEHAVIOR: 630 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS), 631 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK, 632 } 633 634 @classmethod
635 - def _WriteConfigFile(cls, instance, startup_memory, block_devices):
636 """Write the Xen config file for the instance. 637 638 """ 639 hvp = instance.hvparams 640 config = StringIO() 641 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n") 642 643 # if bootloader is True, use bootloader instead of kernel and ramdisk 644 # parameters. 645 if hvp[constants.HV_USE_BOOTLOADER]: 646 # bootloader handling 647 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH] 648 if bootloader_path: 649 config.write("bootloader = '%s'\n" % bootloader_path) 650 else: 651 raise errors.HypervisorError("Bootloader enabled, but missing" 652 " bootloader path") 653 654 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS] 655 if bootloader_args: 656 config.write("bootargs = '%s'\n" % bootloader_args) 657 else: 658 # kernel handling 659 kpath = hvp[constants.HV_KERNEL_PATH] 660 config.write("kernel = '%s'\n" % kpath) 661 662 # initrd handling 663 initrd_path = hvp[constants.HV_INITRD_PATH] 664 if initrd_path: 665 config.write("ramdisk = '%s'\n" % initrd_path) 666 667 # rest of the settings 668 config.write("memory = %d\n" % startup_memory) 669 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM]) 670 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 671 cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK]) 672 if cpu_pinning: 673 config.write("%s\n" % cpu_pinning) 674 675 config.write("name = '%s'\n" % instance.name) 676 677 vif_data = [] 678 for nic in instance.nics: 679 nic_str = "mac=%s" % (nic.mac) 680 ip = getattr(nic, "ip", None) 681 if ip is not None: 682 nic_str += ", ip=%s" % ip 683 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 684 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK] 685 vif_data.append("'%s'" % nic_str) 686 687 disk_data = cls._GetConfigFileDiskData(block_devices, 688 hvp[constants.HV_BLOCKDEV_PREFIX]) 689 690 config.write("vif = [%s]\n" % ",".join(vif_data)) 691 config.write("disk = [%s]\n" % ",".join(disk_data)) 692 693 if hvp[constants.HV_ROOT_PATH]: 694 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH]) 695 config.write("on_poweroff = 'destroy'\n") 696 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED: 697 config.write("on_reboot = 'restart'\n") 698 else: 699 config.write("on_reboot = 'destroy'\n") 700 config.write("on_crash = 'restart'\n") 701 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS]) 702 cls._WriteConfigFileStatic(instance.name, config.getvalue()) 703 704 return True
705
706 707 -class XenHvmHypervisor(XenHypervisor):
708 """Xen HVM hypervisor interface""" 709 710 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [ 711 constants.VNC_PASSWORD_FILE, 712 ] 713 ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [ 714 constants.VNC_PASSWORD_FILE, 715 ] 716 717 PARAMETERS = { 718 constants.HV_ACPI: hv_base.NO_CHECK, 719 constants.HV_BOOT_ORDER: (True, ) + 720 (lambda x: x and len(x.strip("acdn")) == 0, 721 "Invalid boot order specified, must be one or more of [acdn]", 722 None, None), 723 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK, 724 constants.HV_DISK_TYPE: 725 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES), 726 constants.HV_NIC_TYPE: 727 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES), 728 constants.HV_PAE: hv_base.NO_CHECK, 729 constants.HV_VNC_BIND_ADDRESS: 730 (False, netutils.IP4Address.IsValid, 731 "VNC bind address is not a valid IP address", None, None), 732 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 733 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK, 734 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK, 735 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK, 736 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 737 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK, 738 # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar). 739 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK, 740 constants.HV_REBOOT_BEHAVIOR: 741 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS), 742 constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK, 743 } 744 745 @classmethod
746 - def _WriteConfigFile(cls, instance, startup_memory, block_devices):
747 """Create a Xen 3.1 HVM config file. 748 749 """ 750 hvp = instance.hvparams 751 752 config = StringIO() 753 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n") 754 755 # kernel handling 756 kpath = hvp[constants.HV_KERNEL_PATH] 757 config.write("kernel = '%s'\n" % kpath) 758 759 config.write("builder = 'hvm'\n") 760 config.write("memory = %d\n" % startup_memory) 761 config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM]) 762 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 763 cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK]) 764 if cpu_pinning: 765 config.write("%s\n" % cpu_pinning) 766 767 config.write("name = '%s'\n" % instance.name) 768 if hvp[constants.HV_PAE]: 769 config.write("pae = 1\n") 770 else: 771 config.write("pae = 0\n") 772 if hvp[constants.HV_ACPI]: 773 config.write("acpi = 1\n") 774 else: 775 config.write("acpi = 0\n") 776 config.write("apic = 1\n") 777 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL]) 778 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER]) 779 config.write("sdl = 0\n") 780 config.write("usb = 1\n") 781 config.write("usbdevice = 'tablet'\n") 782 config.write("vnc = 1\n") 783 if hvp[constants.HV_VNC_BIND_ADDRESS] is None: 784 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS) 785 else: 786 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS]) 787 788 if instance.network_port > constants.VNC_BASE_PORT: 789 display = instance.network_port - constants.VNC_BASE_PORT 790 config.write("vncdisplay = %s\n" % display) 791 config.write("vncunused = 0\n") 792 else: 793 config.write("# vncdisplay = 1\n") 794 config.write("vncunused = 1\n") 795 796 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE] 797 try: 798 password = utils.ReadFile(vnc_pwd_file) 799 except EnvironmentError, err: 800 raise errors.HypervisorError("Failed to open VNC password file %s: %s" % 801 (vnc_pwd_file, err)) 802 803 config.write("vncpasswd = '%s'\n" % password.rstrip()) 804 805 config.write("serial = 'pty'\n") 806 if hvp[constants.HV_USE_LOCALTIME]: 807 config.write("localtime = 1\n") 808 809 vif_data = [] 810 nic_type = hvp[constants.HV_NIC_TYPE] 811 if nic_type is None: 812 # ensure old instances don't change 813 nic_type_str = ", type=ioemu" 814 elif nic_type == constants.HT_NIC_PARAVIRTUAL: 815 nic_type_str = ", type=paravirtualized" 816 else: 817 nic_type_str = ", model=%s, type=ioemu" % nic_type 818 for nic in instance.nics: 819 nic_str = "mac=%s%s" % (nic.mac, nic_type_str) 820 ip = getattr(nic, "ip", None) 821 if ip is not None: 822 nic_str += ", ip=%s" % ip 823 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 824 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK] 825 vif_data.append("'%s'" % nic_str) 826 827 config.write("vif = [%s]\n" % ",".join(vif_data)) 828 829 disk_data = cls._GetConfigFileDiskData(block_devices, 830 hvp[constants.HV_BLOCKDEV_PREFIX]) 831 832 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH] 833 if iso_path: 834 iso = "'file:%s,hdc:cdrom,r'" % iso_path 835 disk_data.append(iso) 836 837 config.write("disk = [%s]\n" % (",".join(disk_data))) 838 839 config.write("on_poweroff = 'destroy'\n") 840 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED: 841 config.write("on_reboot = 'restart'\n") 842 else: 843 config.write("on_reboot = 'destroy'\n") 844 config.write("on_crash = 'restart'\n") 845 cls._WriteConfigFileStatic(instance.name, config.getvalue()) 846 847 return True
848