1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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"
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
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
78 """Write the Xen config file for the instance.
79
80 """
81 raise NotImplementedError
82
83 @staticmethod
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
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
110
111 @staticmethod
117
118 @classmethod
120 """Create a CPU config string that's compatible with Xen's
121 configuration file.
122
123 """
124
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
131
132 return None
133 else:
134
135
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
146
147
148 return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
149
150 @staticmethod
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
163 return result.stdout.splitlines()[1:]
164
165 @classmethod
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
192
193
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
208 if include_node or data[0] != _DOM0_NAME:
209 result.append(data)
210
211 return result
212
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
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
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):
262
263 - def StopInstance(self, instance, force=False, retry=False, name=None):
279
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
336
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
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
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
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
426
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
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
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
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
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
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
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
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
572 if success:
573
574 try:
575 self._RemoveConfigFile(instance.name)
576 except EnvironmentError:
577 logging.exception("Failure while removing instance config file")
578
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
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
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
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
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
644
645 if hvp[constants.HV_USE_BOOTLOADER]:
646
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
659 kpath = hvp[constants.HV_KERNEL_PATH]
660 config.write("kernel = '%s'\n" % kpath)
661
662
663 initrd_path = hvp[constants.HV_INITRD_PATH]
664 if initrd_path:
665 config.write("ramdisk = '%s'\n" % initrd_path)
666
667
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
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
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
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
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
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