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 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 35 36 -class XenHypervisor(hv_base.BaseHypervisor):
37 """Xen generic hypervisor interface 38 39 This is the Xen base class used for both Xen PVM and HVM. It contains 40 all the functionality that is identical for both. 41 42 """ 43 CAN_MIGRATE = True 44 REBOOT_RETRY_COUNT = 60 45 REBOOT_RETRY_INTERVAL = 10 46 47 ANCILLARY_FILES = [ 48 '/etc/xen/xend-config.sxp', 49 '/etc/xen/scripts/vif-bridge', 50 ] 51 52 @classmethod
53 - def _WriteConfigFile(cls, instance, block_devices):
54 """Write the Xen config file for the instance. 55 56 """ 57 raise NotImplementedError
58 59 @staticmethod
60 - def _WriteConfigFileStatic(instance_name, data):
61 """Write the Xen config file for the instance. 62 63 This version of the function just writes the config file from static data. 64 65 """ 66 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
67 68 @staticmethod
69 - def _ReadConfigFile(instance_name):
70 """Returns the contents of the instance config file. 71 72 """ 73 try: 74 file_content = utils.ReadFile("/etc/xen/%s" % instance_name) 75 except EnvironmentError, err: 76 raise errors.HypervisorError("Failed to load Xen config file: %s" % err) 77 return file_content
78 79 @staticmethod
80 - def _RemoveConfigFile(instance_name):
81 """Remove the xen configuration file. 82 83 """ 84 utils.RemoveFile("/etc/xen/%s" % instance_name)
85 86 @staticmethod
87 - def _RunXmList(xmlist_errors):
88 """Helper function for L{_GetXMList} to run "xm list". 89 90 """ 91 result = utils.RunCmd(["xm", "list"]) 92 if result.failed: 93 logging.error("xm list failed (%s): %s", result.fail_reason, 94 result.output) 95 xmlist_errors.append(result) 96 raise utils.RetryAgain() 97 98 # skip over the heading 99 return result.stdout.splitlines()[1:]
100 101 @classmethod
102 - def _GetXMList(cls, include_node):
103 """Return the list of running instances. 104 105 If the include_node argument is True, then we return information 106 for dom0 also, otherwise we filter that from the return value. 107 108 @return: list of (name, id, memory, vcpus, state, time spent) 109 110 """ 111 xmlist_errors = [] 112 try: 113 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, )) 114 except utils.RetryTimeout: 115 if xmlist_errors: 116 xmlist_result = xmlist_errors.pop() 117 118 errmsg = ("xm list failed, timeout exceeded (%s): %s" % 119 (xmlist_result.fail_reason, xmlist_result.output)) 120 else: 121 errmsg = "xm list failed" 122 123 raise errors.HypervisorError(errmsg) 124 125 result = [] 126 for line in lines: 127 # The format of lines is: 128 # Name ID Mem(MiB) VCPUs State Time(s) 129 # Domain-0 0 3418 4 r----- 266.2 130 data = line.split() 131 if len(data) != 6: 132 raise errors.HypervisorError("Can't parse output of xm list," 133 " line: %s" % line) 134 try: 135 data[1] = int(data[1]) 136 data[2] = int(data[2]) 137 data[3] = int(data[3]) 138 data[5] = float(data[5]) 139 except (TypeError, ValueError), err: 140 raise errors.HypervisorError("Can't parse output of xm list," 141 " line: %s, error: %s" % (line, err)) 142 143 # skip the Domain-0 (optional) 144 if include_node or data[0] != 'Domain-0': 145 result.append(data) 146 147 return result
148
149 - def ListInstances(self):
150 """Get the list of running instances. 151 152 """ 153 xm_list = self._GetXMList(False) 154 names = [info[0] for info in xm_list] 155 return names
156
157 - def GetInstanceInfo(self, instance_name):
158 """Get instance properties. 159 160 @param instance_name: the instance name 161 162 @return: tuple (name, id, memory, vcpus, stat, times) 163 164 """ 165 xm_list = self._GetXMList(instance_name=="Domain-0") 166 result = None 167 for data in xm_list: 168 if data[0] == instance_name: 169 result = data 170 break 171 return result
172
173 - def GetAllInstancesInfo(self):
174 """Get properties of all instances. 175 176 @return: list of tuples (name, id, memory, vcpus, stat, times) 177 178 """ 179 xm_list = self._GetXMList(False) 180 return xm_list
181
182 - def StartInstance(self, instance, block_devices):
183 """Start an instance. 184 185 """ 186 self._WriteConfigFile(instance, block_devices) 187 result = utils.RunCmd(["xm", "create", instance.name]) 188 189 if result.failed: 190 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" % 191 (instance.name, result.fail_reason, 192 result.output))
193
194 - def StopInstance(self, instance, force=False, retry=False, name=None):
195 """Stop an instance. 196 197 """ 198 if name is None: 199 name = instance.name 200 self._RemoveConfigFile(name) 201 if force: 202 command = ["xm", "destroy", name] 203 else: 204 command = ["xm", "shutdown", name] 205 result = utils.RunCmd(command) 206 207 if result.failed: 208 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" % 209 (name, result.fail_reason, result.output))
210
211 - def RebootInstance(self, instance):
212 """Reboot an instance. 213 214 """ 215 ini_info = self.GetInstanceInfo(instance.name) 216 217 if ini_info is None: 218 raise errors.HypervisorError("Failed to reboot instance %s," 219 " not running" % instance.name) 220 221 result = utils.RunCmd(["xm", "reboot", instance.name]) 222 if result.failed: 223 raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" % 224 (instance.name, result.fail_reason, 225 result.output)) 226 227 def _CheckInstance(): 228 new_info = self.GetInstanceInfo(instance.name) 229 230 # check if the domain ID has changed or the run time has decreased 231 if (new_info is not None and 232 (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])): 233 return 234 235 raise utils.RetryAgain()
236 237 try: 238 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL, 239 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT) 240 except utils.RetryTimeout: 241 raise errors.HypervisorError("Failed to reboot instance %s: instance" 242 " did not reboot in the expected interval" % 243 (instance.name, ))
244
245 - def GetNodeInfo(self):
246 """Return information about the node. 247 248 @return: a dict with the following keys (memory values in MiB): 249 - memory_total: the total memory size on the node 250 - memory_free: the available memory on the node for instances 251 - memory_dom0: the memory used by the node itself, if available 252 - nr_cpus: total number of CPUs 253 - nr_nodes: in a NUMA system, the number of domains 254 - nr_sockets: the number of physical CPU sockets in the node 255 256 """ 257 # note: in xen 3, memory has changed to total_memory 258 result = utils.RunCmd(["xm", "info"]) 259 if result.failed: 260 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason, 261 result.output) 262 return None 263 264 xmoutput = result.stdout.splitlines() 265 result = {} 266 cores_per_socket = threads_per_core = nr_cpus = None 267 for line in xmoutput: 268 splitfields = line.split(":", 1) 269 270 if len(splitfields) > 1: 271 key = splitfields[0].strip() 272 val = splitfields[1].strip() 273 if key == 'memory' or key == 'total_memory': 274 result['memory_total'] = int(val) 275 elif key == 'free_memory': 276 result['memory_free'] = int(val) 277 elif key == 'nr_cpus': 278 nr_cpus = result['cpu_total'] = int(val) 279 elif key == 'nr_nodes': 280 result['cpu_nodes'] = int(val) 281 elif key == 'cores_per_socket': 282 cores_per_socket = int(val) 283 elif key == 'threads_per_core': 284 threads_per_core = int(val) 285 286 if (cores_per_socket is not None and 287 threads_per_core is not None and nr_cpus is not None): 288 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core) 289 290 dom0_info = self.GetInstanceInfo("Domain-0") 291 if dom0_info is not None: 292 result['memory_dom0'] = dom0_info[2] 293 294 return result
295 296 @classmethod
297 - def GetShellCommandForConsole(cls, instance, hvparams, beparams):
298 """Return a command for connecting to the console of an instance. 299 300 """ 301 return "xm console %s" % instance.name
302 303
304 - def Verify(self):
305 """Verify the hypervisor. 306 307 For Xen, this verifies that the xend process is running. 308 309 """ 310 result = utils.RunCmd(["xm", "info"]) 311 if result.failed: 312 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
313 314 @staticmethod
315 - def _GetConfigFileDiskData(block_devices):
316 """Get disk directive for xen config file. 317 318 This method builds the xen config disk directive according to the 319 given disk_template and block_devices. 320 321 @param block_devices: list of tuples (cfdev, rldev): 322 - cfdev: dict containing ganeti config disk part 323 - rldev: ganeti.bdev.BlockDev object 324 325 @return: string containing disk directive for xen instance config file 326 327 """ 328 FILE_DRIVER_MAP = { 329 constants.FD_LOOP: "file", 330 constants.FD_BLKTAP: "tap:aio", 331 } 332 disk_data = [] 333 if len(block_devices) > 24: 334 # 'z' - 'a' = 24 335 raise errors.HypervisorError("Too many disks") 336 # FIXME: instead of this hardcoding here, each of PVM/HVM should 337 # directly export their info (currently HVM will just sed this info) 338 namespace = ["sd" + chr(i + ord('a')) for i in range(24)] 339 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices): 340 if cfdev.mode == constants.DISK_RDWR: 341 mode = "w" 342 else: 343 mode = "r" 344 if cfdev.dev_type == constants.LD_FILE: 345 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]], 346 dev_path, sd_name, mode) 347 else: 348 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode) 349 disk_data.append(line) 350 351 return disk_data
352
353 - def MigrationInfo(self, instance):
354 """Get instance information to perform a migration. 355 356 @type instance: L{objects.Instance} 357 @param instance: instance to be migrated 358 @rtype: string 359 @return: content of the xen config file 360 361 """ 362 return self._ReadConfigFile(instance.name)
363
364 - def AcceptInstance(self, instance, info, target):
365 """Prepare to accept an instance. 366 367 @type instance: L{objects.Instance} 368 @param instance: instance to be accepted 369 @type info: string 370 @param info: content of the xen config file on the source node 371 @type target: string 372 @param target: target host (usually ip), on this node 373 374 """ 375 pass
376
377 - def FinalizeMigration(self, instance, info, success):
378 """Finalize an instance migration. 379 380 After a successful migration we write the xen config file. 381 We do nothing on a failure, as we did not change anything at accept time. 382 383 @type instance: L{objects.Instance} 384 @param instance: instance whose migration is being finalized 385 @type info: string 386 @param info: content of the xen config file on the source node 387 @type success: boolean 388 @param success: whether the migration was a success or a failure 389 390 """ 391 if success: 392 self._WriteConfigFileStatic(instance.name, info)
393
394 - def MigrateInstance(self, instance, target, live):
395 """Migrate an instance to a target node. 396 397 The migration will not be attempted if the instance is not 398 currently running. 399 400 @type instance: L{objects.Instance} 401 @param instance: the instance to be migrated 402 @type target: string 403 @param target: ip address of the target node 404 @type live: boolean 405 @param live: perform a live migration 406 407 """ 408 if self.GetInstanceInfo(instance.name) is None: 409 raise errors.HypervisorError("Instance not running, cannot migrate") 410 411 port = instance.hvparams[constants.HV_MIGRATION_PORT] 412 413 if not netutils.TcpPing(target, port, live_port_needed=True): 414 raise errors.HypervisorError("Remote host %s not listening on port" 415 " %s, cannot migrate" % (target, port)) 416 417 args = ["xm", "migrate", "-p", "%d" % port] 418 if live: 419 args.append("-l") 420 args.extend([instance.name, target]) 421 result = utils.RunCmd(args) 422 if result.failed: 423 raise errors.HypervisorError("Failed to migrate instance %s: %s" % 424 (instance.name, result.output)) 425 # remove old xen file after migration succeeded 426 try: 427 self._RemoveConfigFile(instance.name) 428 except EnvironmentError: 429 logging.exception("Failure while removing instance config file")
430 431 @classmethod
432 - def PowercycleNode(cls):
433 """Xen-specific powercycle. 434 435 This first does a Linux reboot (which triggers automatically a Xen 436 reboot), and if that fails it tries to do a Xen reboot. The reason 437 we don't try a Xen reboot first is that the xen reboot launches an 438 external command which connects to the Xen hypervisor, and that 439 won't work in case the root filesystem is broken and/or the xend 440 daemon is not working. 441 442 """ 443 try: 444 cls.LinuxPowercycle() 445 finally: 446 utils.RunCmd(["xm", "debug", "R"])
447
448 449 -class XenPvmHypervisor(XenHypervisor):
450 """Xen PVM hypervisor interface""" 451 452 PARAMETERS = { 453 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK, 454 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK, 455 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK, 456 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 457 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK, 458 constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK, 459 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK, 460 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK, 461 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 462 } 463 464 @classmethod
465 - def _WriteConfigFile(cls, instance, block_devices):
466 """Write the Xen config file for the instance. 467 468 """ 469 hvp = instance.hvparams 470 config = StringIO() 471 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n") 472 473 # if bootloader is True, use bootloader instead of kernel and ramdisk 474 # parameters. 475 if hvp[constants.HV_USE_BOOTLOADER]: 476 # bootloader handling 477 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH] 478 if bootloader_path: 479 config.write("bootloader = '%s'\n" % bootloader_path) 480 else: 481 raise errors.HypervisorError("Bootloader enabled, but missing" 482 " bootloader path") 483 484 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS] 485 if bootloader_args: 486 config.write("bootargs = '%s'\n" % bootloader_args) 487 else: 488 # kernel handling 489 kpath = hvp[constants.HV_KERNEL_PATH] 490 config.write("kernel = '%s'\n" % kpath) 491 492 # initrd handling 493 initrd_path = hvp[constants.HV_INITRD_PATH] 494 if initrd_path: 495 config.write("ramdisk = '%s'\n" % initrd_path) 496 497 # rest of the settings 498 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY]) 499 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 500 config.write("name = '%s'\n" % instance.name) 501 502 vif_data = [] 503 for nic in instance.nics: 504 nic_str = "mac=%s" % (nic.mac) 505 ip = getattr(nic, "ip", None) 506 if ip is not None: 507 nic_str += ", ip=%s" % ip 508 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 509 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK] 510 vif_data.append("'%s'" % nic_str) 511 512 disk_data = cls._GetConfigFileDiskData(block_devices) 513 514 config.write("vif = [%s]\n" % ",".join(vif_data)) 515 config.write("disk = [%s]\n" % ",".join(disk_data)) 516 517 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH]) 518 config.write("on_poweroff = 'destroy'\n") 519 config.write("on_reboot = 'restart'\n") 520 config.write("on_crash = 'restart'\n") 521 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS]) 522 # just in case it exists 523 utils.RemoveFile("/etc/xen/auto/%s" % instance.name) 524 try: 525 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue()) 526 except EnvironmentError, err: 527 raise errors.HypervisorError("Cannot write Xen instance confile" 528 " file /etc/xen/%s: %s" % 529 (instance.name, err)) 530 531 return True
532
533 534 -class XenHvmHypervisor(XenHypervisor):
535 """Xen HVM hypervisor interface""" 536 537 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [ 538 constants.VNC_PASSWORD_FILE, 539 ] 540 541 PARAMETERS = { 542 constants.HV_ACPI: hv_base.NO_CHECK, 543 constants.HV_BOOT_ORDER: (True, ) + 544 (lambda x: x and len(x.strip("acdn")) == 0, 545 "Invalid boot order specified, must be one or more of [acdn]", 546 None, None), 547 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK, 548 constants.HV_DISK_TYPE: 549 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES), 550 constants.HV_NIC_TYPE: 551 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES), 552 constants.HV_PAE: hv_base.NO_CHECK, 553 constants.HV_VNC_BIND_ADDRESS: 554 (False, netutils.IsValidIP4, 555 "VNC bind address is not a valid IP address", None, None), 556 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK, 557 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK, 558 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK, 559 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK, 560 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, 561 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK, 562 } 563 564 @classmethod
565 - def _WriteConfigFile(cls, instance, block_devices):
566 """Create a Xen 3.1 HVM config file. 567 568 """ 569 hvp = instance.hvparams 570 571 config = StringIO() 572 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n") 573 574 # kernel handling 575 kpath = hvp[constants.HV_KERNEL_PATH] 576 config.write("kernel = '%s'\n" % kpath) 577 578 config.write("builder = 'hvm'\n") 579 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY]) 580 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS]) 581 config.write("name = '%s'\n" % instance.name) 582 if hvp[constants.HV_PAE]: 583 config.write("pae = 1\n") 584 else: 585 config.write("pae = 0\n") 586 if hvp[constants.HV_ACPI]: 587 config.write("acpi = 1\n") 588 else: 589 config.write("acpi = 0\n") 590 config.write("apic = 1\n") 591 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL]) 592 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER]) 593 config.write("sdl = 0\n") 594 config.write("usb = 1\n") 595 config.write("usbdevice = 'tablet'\n") 596 config.write("vnc = 1\n") 597 if hvp[constants.HV_VNC_BIND_ADDRESS] is None: 598 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS) 599 else: 600 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS]) 601 602 if instance.network_port > constants.VNC_BASE_PORT: 603 display = instance.network_port - constants.VNC_BASE_PORT 604 config.write("vncdisplay = %s\n" % display) 605 config.write("vncunused = 0\n") 606 else: 607 config.write("# vncdisplay = 1\n") 608 config.write("vncunused = 1\n") 609 610 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE] 611 try: 612 password = utils.ReadFile(vnc_pwd_file) 613 except EnvironmentError, err: 614 raise errors.HypervisorError("Failed to open VNC password file %s: %s" % 615 (vnc_pwd_file, err)) 616 617 config.write("vncpasswd = '%s'\n" % password.rstrip()) 618 619 config.write("serial = 'pty'\n") 620 if hvp[constants.HV_USE_LOCALTIME]: 621 config.write("localtime = 1\n") 622 623 vif_data = [] 624 nic_type = hvp[constants.HV_NIC_TYPE] 625 if nic_type is None: 626 # ensure old instances don't change 627 nic_type_str = ", type=ioemu" 628 elif nic_type == constants.HT_NIC_PARAVIRTUAL: 629 nic_type_str = ", type=paravirtualized" 630 else: 631 nic_type_str = ", model=%s, type=ioemu" % nic_type 632 for nic in instance.nics: 633 nic_str = "mac=%s%s" % (nic.mac, nic_type_str) 634 ip = getattr(nic, "ip", None) 635 if ip is not None: 636 nic_str += ", ip=%s" % ip 637 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED: 638 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK] 639 vif_data.append("'%s'" % nic_str) 640 641 config.write("vif = [%s]\n" % ",".join(vif_data)) 642 disk_data = cls._GetConfigFileDiskData(block_devices) 643 disk_type = hvp[constants.HV_DISK_TYPE] 644 if disk_type in (None, constants.HT_DISK_IOEMU): 645 replacement = ",ioemu:hd" 646 else: 647 replacement = ",hd" 648 disk_data = [line.replace(",sd", replacement) for line in disk_data] 649 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH] 650 if iso_path: 651 iso = "'file:%s,hdc:cdrom,r'" % iso_path 652 disk_data.append(iso) 653 654 config.write("disk = [%s]\n" % (",".join(disk_data))) 655 656 config.write("on_poweroff = 'destroy'\n") 657 config.write("on_reboot = 'restart'\n") 658 config.write("on_crash = 'restart'\n") 659 # just in case it exists 660 utils.RemoveFile("/etc/xen/auto/%s" % instance.name) 661 try: 662 utils.WriteFile("/etc/xen/%s" % instance.name, 663 data=config.getvalue()) 664 except EnvironmentError, err: 665 raise errors.HypervisorError("Cannot write Xen instance confile" 666 " file /etc/xen/%s: %s" % 667 (instance.name, err)) 668 669 return True
670