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