Add VALUE_AUTO and VALUE_GENERATE constants
[ganeti-local] / lib / hypervisor.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 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 """Module that abstracts the virtualisation interface
23
24 """
25
26 import time
27 import os
28 import re
29 from cStringIO import StringIO
30
31 from ganeti import utils
32 from ganeti import logger
33 from ganeti import ssconf
34 from ganeti import constants
35 from ganeti import errors
36 from ganeti.errors import HypervisorError
37
38
39 def GetHypervisor():
40   """Return a Hypervisor instance.
41
42   This function parses the cluster hypervisor configuration file and
43   instantiates a class based on the value of this file.
44
45   """
46   ht_kind = ssconf.SimpleStore().GetHypervisorType()
47   if ht_kind == constants.HT_XEN_PVM30:
48     cls = XenPvmHypervisor
49   elif ht_kind == constants.HT_FAKE:
50     cls = FakeHypervisor
51   elif ht_kind == constants.HT_XEN_HVM31:
52     cls = XenHvmHypervisor
53   else:
54     raise HypervisorError("Unknown hypervisor type '%s'" % ht_kind)
55   return cls()
56
57
58 class BaseHypervisor(object):
59   """Abstract virtualisation technology interface
60
61   The goal is that all aspects of the virtualisation technology must
62   be abstracted away from the rest of code.
63
64   """
65   def __init__(self):
66     pass
67
68   def StartInstance(self, instance, block_devices, extra_args):
69     """Start an instance."""
70     raise NotImplementedError
71
72   def StopInstance(self, instance, force=False):
73     """Stop an instance."""
74     raise NotImplementedError
75
76   def RebootInstance(self, instance):
77     """Reboot an instance."""
78     raise NotImplementedError
79
80   def ListInstances(self):
81     """Get the list of running instances."""
82     raise NotImplementedError
83
84   def GetInstanceInfo(self, instance_name):
85     """Get instance properties.
86
87     Args:
88       instance_name: the instance name
89
90     Returns:
91       (name, id, memory, vcpus, state, times)
92
93     """
94     raise NotImplementedError
95
96   def GetAllInstancesInfo(self):
97     """Get properties of all instances.
98
99     Returns:
100       [(name, id, memory, vcpus, stat, times),...]
101     """
102     raise NotImplementedError
103
104   def GetNodeInfo(self):
105     """Return information about the node.
106
107     The return value is a dict, which has to have the following items:
108       (all values in MiB)
109       - memory_total: the total memory size on the node
110       - memory_free: the available memory on the node for instances
111       - memory_dom0: the memory used by the node itself, if available
112
113     """
114     raise NotImplementedError
115
116   @staticmethod
117   def GetShellCommandForConsole(instance):
118     """Return a command for connecting to the console of an instance.
119
120     """
121     raise NotImplementedError
122
123   def Verify(self):
124     """Verify the hypervisor.
125
126     """
127     raise NotImplementedError
128
129   def MigrateInstance(self, name, target, live):
130     """Migrate an instance.
131
132     Arguments:
133       - name: the name of the instance
134       - target: the target of the migration (usually will be IP and not name)
135       - live: whether to do live migration or not
136
137     Returns: none, errors will be signaled by exception.
138
139     """
140     raise NotImplementedError
141
142
143 class XenHypervisor(BaseHypervisor):
144   """Xen generic hypervisor interface
145
146   This is the Xen base class used for both Xen PVM and HVM. It contains
147   all the functionality that is identical for both.
148
149   """
150
151   @staticmethod
152   def _WriteConfigFile(instance, block_devices, extra_args):
153     """Write the Xen config file for the instance.
154
155     """
156     raise NotImplementedError
157
158   @staticmethod
159   def _RemoveConfigFile(instance_name):
160     """Remove the xen configuration file.
161
162     """
163     utils.RemoveFile("/etc/xen/%s" % instance_name)
164
165   @staticmethod
166   def _GetXMList(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     The return value is a list of (name, id, memory, vcpus, state, time spent)
173
174     """
175     for dummy in range(5):
176       result = utils.RunCmd(["xm", "list"])
177       if not result.failed:
178         break
179       logger.Error("xm list failed (%s): %s" % (result.fail_reason,
180                                                 result.output))
181       time.sleep(1)
182
183     if result.failed:
184       raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
185                             (result.fail_reason, result.stderr))
186
187     # skip over the heading
188     lines = result.stdout.splitlines()[1:]
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 HypervisorError("Can't parse output of xm list, line: %s" % line)
197       try:
198         data[1] = int(data[1])
199         data[2] = int(data[2])
200         data[3] = int(data[3])
201         data[5] = float(data[5])
202       except ValueError, err:
203         raise HypervisorError("Can't parse output of xm list,"
204                               " line: %s, error: %s" % (line, err))
205
206       # skip the Domain-0 (optional)
207       if include_node or data[0] != 'Domain-0':
208         result.append(data)
209
210     return result
211
212   def ListInstances(self):
213     """Get the list of running instances.
214
215     """
216     xm_list = self._GetXMList(False)
217     names = [info[0] for info in xm_list]
218     return names
219
220   def GetInstanceInfo(self, instance_name):
221     """Get instance properties.
222
223     Args:
224       instance_name: the instance name
225
226     Returns:
227       (name, id, memory, vcpus, stat, times)
228     """
229     xm_list = self._GetXMList(instance_name=="Domain-0")
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     Returns:
241       [(name, id, memory, vcpus, stat, times),...]
242     """
243     xm_list = self._GetXMList(False)
244     return xm_list
245
246   def StartInstance(self, instance, block_devices, extra_args):
247     """Start an instance."""
248     self._WriteConfigFile(instance, block_devices, extra_args)
249     result = utils.RunCmd(["xm", "create", instance.name])
250
251     if result.failed:
252       raise HypervisorError("Failed to start instance %s: %s (%s)" %
253                             (instance.name, result.fail_reason, result.output))
254
255   def StopInstance(self, instance, force=False):
256     """Stop an instance."""
257     self._RemoveConfigFile(instance.name)
258     if force:
259       command = ["xm", "destroy", instance.name]
260     else:
261       command = ["xm", "shutdown", instance.name]
262     result = utils.RunCmd(command)
263
264     if result.failed:
265       raise HypervisorError("Failed to stop instance %s: %s" %
266                             (instance.name, result.fail_reason))
267
268   def RebootInstance(self, instance):
269     """Reboot an instance."""
270     result = utils.RunCmd(["xm", "reboot", instance.name])
271
272     if result.failed:
273       raise HypervisorError("Failed to reboot instance %s: %s" %
274                             (instance.name, result.fail_reason))
275
276   def GetNodeInfo(self):
277     """Return information about the node.
278
279     The return value is a dict, which has to have the following items:
280       (memory values in MiB)
281       - memory_total: the total memory size on the node
282       - memory_free: the available memory on the node for instances
283       - memory_dom0: the memory used by the node itself, if available
284       - nr_cpus: total number of CPUs
285       - nr_nodes: in a NUMA system, the number of domains
286       - nr_sockets: the number of physical CPU sockets in the node
287
288     """
289     # note: in xen 3, memory has changed to total_memory
290     result = utils.RunCmd(["xm", "info"])
291     if result.failed:
292       logger.Error("Can't run 'xm info': %s" % result.fail_reason)
293       return None
294
295     xmoutput = result.stdout.splitlines()
296     result = {}
297     cores_per_socket = threads_per_core = nr_cpus = None
298     for line in xmoutput:
299       splitfields = line.split(":", 1)
300
301       if len(splitfields) > 1:
302         key = splitfields[0].strip()
303         val = splitfields[1].strip()
304         if key == 'memory' or key == 'total_memory':
305           result['memory_total'] = int(val)
306         elif key == 'free_memory':
307           result['memory_free'] = int(val)
308         elif key == 'nr_cpus':
309           nr_cpus = result['cpu_total'] = int(val)
310         elif key == 'nr_nodes':
311           result['cpu_nodes'] = int(val)
312         elif key == 'cores_per_socket':
313           cores_per_socket = int(val)
314         elif key == 'threads_per_core':
315           threads_per_core = int(val)
316
317     if (cores_per_socket is not None and
318         threads_per_core is not None and nr_cpus is not None):
319       result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
320
321     dom0_info = self.GetInstanceInfo("Domain-0")
322     if dom0_info is not None:
323       result['memory_dom0'] = dom0_info[2]
324
325     return result
326
327   @staticmethod
328   def GetShellCommandForConsole(instance):
329     """Return a command for connecting to the console of an instance.
330
331     """
332     return "xm console %s" % instance.name
333
334
335
336   def Verify(self):
337     """Verify the hypervisor.
338
339     For Xen, this verifies that the xend process is running.
340
341     """
342     if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
343       return "xend daemon is not running"
344
345   def MigrateInstance(self, instance, target, live):
346     """Migrate an instance to a target node.
347
348     Arguments:
349       - instance: the name of the instance
350       - target: the ip of the target node
351       - live: whether to do live migration or not
352
353     Returns: none, errors will be signaled by exception.
354
355     The migration will not be attempted if the instance is not
356     currently running.
357
358     """
359     if self.GetInstanceInfo(instance) is None:
360       raise errors.HypervisorError("Instance not running, cannot migrate")
361     args = ["xm", "migrate"]
362     if live:
363       args.append("-l")
364     args.extend([instance, target])
365     result = utils.RunCmd(args)
366     if result.failed:
367       raise errors.HypervisorError("Failed to migrate instance %s: %s" %
368                                    (instance, result.output))
369     # remove old xen file after migration succeeded
370     try:
371       self._RemoveConfigFile(instance)
372     except EnvironmentError, err:
373       logger.Error("Failure while removing instance config file: %s" %
374                    str(err))
375
376
377 class XenPvmHypervisor(XenHypervisor):
378   """Xen PVM hypervisor interface"""
379
380   @staticmethod
381   def _WriteConfigFile(instance, block_devices, extra_args):
382     """Write the Xen config file for the instance.
383
384     """
385     config = StringIO()
386     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
387
388     # kernel handling
389     if instance.kernel_path in (None, constants.VALUE_DEFAULT):
390       kpath = constants.XEN_KERNEL
391     else:
392       if not os.path.exists(instance.kernel_path):
393         raise errors.HypervisorError("The kernel %s for instance %s is"
394                                      " missing" % (instance.kernel_path,
395                                                    instance.name))
396       kpath = instance.kernel_path
397     config.write("kernel = '%s'\n" % kpath)
398
399     # initrd handling
400     if instance.initrd_path in (None, constants.VALUE_DEFAULT):
401       if os.path.exists(constants.XEN_INITRD):
402         initrd_path = constants.XEN_INITRD
403       else:
404         initrd_path = None
405     elif instance.initrd_path == constants.VALUE_NONE:
406       initrd_path = None
407     else:
408       if not os.path.exists(instance.initrd_path):
409         raise errors.HypervisorError("The initrd %s for instance %s is"
410                                      " missing" % (instance.initrd_path,
411                                                    instance.name))
412       initrd_path = instance.initrd_path
413
414     if initrd_path:
415       config.write("ramdisk = '%s'\n" % initrd_path)
416
417     # rest of the settings
418     config.write("memory = %d\n" % instance.memory)
419     config.write("vcpus = %d\n" % instance.vcpus)
420     config.write("name = '%s'\n" % instance.name)
421
422     vif_data = []
423     for nic in instance.nics:
424       nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
425       ip = getattr(nic, "ip", None)
426       if ip is not None:
427         nic_str += ", ip=%s" % ip
428       vif_data.append("'%s'" % nic_str)
429
430     config.write("vif = [%s]\n" % ",".join(vif_data))
431
432     disk_data = ["'phy:%s,%s,w'" % names for names in block_devices]
433     config.write("disk = [%s]\n" % ",".join(disk_data))
434
435     config.write("root = '/dev/sda ro'\n")
436     config.write("on_poweroff = 'destroy'\n")
437     config.write("on_reboot = 'restart'\n")
438     config.write("on_crash = 'restart'\n")
439     if extra_args:
440       config.write("extra = '%s'\n" % extra_args)
441     # just in case it exists
442     utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
443     try:
444       f = open("/etc/xen/%s" % instance.name, "w")
445       try:
446         f.write(config.getvalue())
447       finally:
448         f.close()
449     except IOError, err:
450       raise errors.OpExecError("Cannot write Xen instance confile"
451                                " file /etc/xen/%s: %s" % (instance.name, err))
452     return True
453
454
455 class FakeHypervisor(BaseHypervisor):
456   """Fake hypervisor interface.
457
458   This can be used for testing the ganeti code without having to have
459   a real virtualisation software installed.
460
461   """
462   _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
463
464   def __init__(self):
465     BaseHypervisor.__init__(self)
466     if not os.path.exists(self._ROOT_DIR):
467       os.mkdir(self._ROOT_DIR)
468
469   def ListInstances(self):
470     """Get the list of running instances.
471
472     """
473     return os.listdir(self._ROOT_DIR)
474
475   def GetInstanceInfo(self, instance_name):
476     """Get instance properties.
477
478     Args:
479       instance_name: the instance name
480
481     Returns:
482       (name, id, memory, vcpus, stat, times)
483     """
484     file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
485     if not os.path.exists(file_name):
486       return None
487     try:
488       fh = file(file_name, "r")
489       try:
490         inst_id = fh.readline().strip()
491         memory = fh.readline().strip()
492         vcpus = fh.readline().strip()
493         stat = "---b-"
494         times = "0"
495         return (instance_name, inst_id, memory, vcpus, stat, times)
496       finally:
497         fh.close()
498     except IOError, err:
499       raise HypervisorError("Failed to list instance %s: %s" %
500                             (instance_name, err))
501
502   def GetAllInstancesInfo(self):
503     """Get properties of all instances.
504
505     Returns:
506       [(name, id, memory, vcpus, stat, times),...]
507     """
508     data = []
509     for file_name in os.listdir(self._ROOT_DIR):
510       try:
511         fh = file(self._ROOT_DIR+"/"+file_name, "r")
512         inst_id = "-1"
513         memory = "0"
514         stat = "-----"
515         times = "-1"
516         try:
517           inst_id = fh.readline().strip()
518           memory = fh.readline().strip()
519           vcpus = fh.readline().strip()
520           stat = "---b-"
521           times = "0"
522         finally:
523           fh.close()
524         data.append((file_name, inst_id, memory, vcpus, stat, times))
525       except IOError, err:
526         raise HypervisorError("Failed to list instances: %s" % err)
527     return data
528
529   def StartInstance(self, instance, block_devices, extra_args):
530     """Start an instance.
531
532     For the fake hypervisor, it just creates a file in the base dir,
533     creating an exception if it already exists. We don't actually
534     handle race conditions properly, since these are *FAKE* instances.
535
536     """
537     file_name = self._ROOT_DIR + "/%s" % instance.name
538     if os.path.exists(file_name):
539       raise HypervisorError("Failed to start instance %s: %s" %
540                             (instance.name, "already running"))
541     try:
542       fh = file(file_name, "w")
543       try:
544         fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
545       finally:
546         fh.close()
547     except IOError, err:
548       raise HypervisorError("Failed to start instance %s: %s" %
549                             (instance.name, err))
550
551   def StopInstance(self, instance, force=False):
552     """Stop an instance.
553
554     For the fake hypervisor, this just removes the file in the base
555     dir, if it exist, otherwise we raise an exception.
556
557     """
558     file_name = self._ROOT_DIR + "/%s" % instance.name
559     if not os.path.exists(file_name):
560       raise HypervisorError("Failed to stop instance %s: %s" %
561                             (instance.name, "not running"))
562     utils.RemoveFile(file_name)
563
564   def RebootInstance(self, instance):
565     """Reboot an instance.
566
567     For the fake hypervisor, this does nothing.
568
569     """
570     return
571
572   def GetNodeInfo(self):
573     """Return information about the node.
574
575     The return value is a dict, which has to have the following items:
576       (all values in MiB)
577       - memory_total: the total memory size on the node
578       - memory_free: the available memory on the node for instances
579       - memory_dom0: the memory used by the node itself, if available
580
581     """
582     # global ram usage from the xm info command
583     # memory                 : 3583
584     # free_memory            : 747
585     # note: in xen 3, memory has changed to total_memory
586     try:
587       fh = file("/proc/meminfo")
588       try:
589         data = fh.readlines()
590       finally:
591         fh.close()
592     except IOError, err:
593       raise HypervisorError("Failed to list node info: %s" % err)
594
595     result = {}
596     sum_free = 0
597     for line in data:
598       splitfields = line.split(":", 1)
599
600       if len(splitfields) > 1:
601         key = splitfields[0].strip()
602         val = splitfields[1].strip()
603         if key == 'MemTotal':
604           result['memory_total'] = int(val.split()[0])/1024
605         elif key in ('MemFree', 'Buffers', 'Cached'):
606           sum_free += int(val.split()[0])/1024
607         elif key == 'Active':
608           result['memory_dom0'] = int(val.split()[0])/1024
609     result['memory_free'] = sum_free
610
611     cpu_total = 0
612     try:
613       fh = open("/proc/cpuinfo")
614       try:
615         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
616                                    fh.read()))
617       finally:
618         fh.close()
619     except EnvironmentError, err:
620       raise HypervisorError("Failed to list node info: %s" % err)
621     result['cpu_total'] = cpu_total
622
623     return result
624
625   @staticmethod
626   def GetShellCommandForConsole(instance):
627     """Return a command for connecting to the console of an instance.
628
629     """
630     return "echo Console not available for fake hypervisor"
631
632   def Verify(self):
633     """Verify the hypervisor.
634
635     For the fake hypervisor, it just checks the existence of the base
636     dir.
637
638     """
639     if not os.path.exists(self._ROOT_DIR):
640       return "The required directory '%s' does not exist." % self._ROOT_DIR
641
642
643 class XenHvmHypervisor(XenHypervisor):
644   """Xen HVM hypervisor interface"""
645
646   @staticmethod
647   def _WriteConfigFile(instance, block_devices, extra_args):
648     """Create a Xen 3.1 HVM config file.
649
650     """
651     config = StringIO()
652     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
653     config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
654     config.write("builder = 'hvm'\n")
655     config.write("memory = %d\n" % instance.memory)
656     config.write("vcpus = %d\n" % instance.vcpus)
657     config.write("name = '%s'\n" % instance.name)
658     if instance.hvm_pae is None:   # use default value if not specified
659       config.write("pae = %s\n" % constants.HT_HVM_DEFAULT_PAE_MODE)
660     elif instance.hvm_pae:
661       config.write("pae = 1\n")
662     else:
663       config.write("pae = 0\n")
664     if instance.hvm_acpi is None:  # use default value if not specified
665       config.write("acpi = %s\n" % constants.HT_HVM_DEFAULT_ACPI_MODE)
666     elif instance.hvm_acpi:
667       config.write("acpi = 1\n")
668     else:
669       config.write("acpi = 0\n")
670     config.write("apic = 1\n")
671     arch = os.uname()[4]
672     if '64' in arch:
673       config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
674     else:
675       config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
676     if instance.hvm_boot_order is None:
677       config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
678     else:
679       config.write("boot = '%s'\n" % instance.hvm_boot_order)
680     config.write("sdl = 0\n")
681     config.write("usb = 1\n")
682     config.write("usbdevice = 'tablet'\n")
683     config.write("vnc = 1\n")
684     if instance.vnc_bind_address is None:
685       config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
686     else:
687       config.write("vnclisten = '%s'\n" % instance.vnc_bind_address)
688
689     if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
690       display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
691       config.write("vncdisplay = %s\n" % display)
692       config.write("vncunused = 0\n")
693     else:
694       config.write("# vncdisplay = 1\n")
695       config.write("vncunused = 1\n")
696
697     try:
698       password_file = open(constants.VNC_PASSWORD_FILE, "r")
699       try:
700         password = password_file.readline()
701       finally:
702         password_file.close()
703     except IOError:
704       raise errors.OpExecError("failed to open VNC password file %s " %
705                                constants.VNC_PASSWORD_FILE)
706
707     config.write("vncpasswd = '%s'\n" % password.rstrip())
708
709     config.write("serial = 'pty'\n")
710     config.write("localtime = 1\n")
711
712     vif_data = []
713     for nic in instance.nics:
714       if instance.hvm_nic_type is None: # ensure old instances don't change
715         nic_type = ", type=ioemu"
716       elif instance.hvm_nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
717         nic_type = ", type=paravirtualized"
718       else:
719         nic_type = ", model=%s, type=ioemu" % instance.hvm_nic_type
720
721       nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type)
722       ip = getattr(nic, "ip", None)
723       if ip is not None:
724         nic_str += ", ip=%s" % ip
725       vif_data.append("'%s'" % nic_str)
726
727     config.write("vif = [%s]\n" % ",".join(vif_data))
728
729     # TODO(2.0): This code changes the block device name, seen by the instance,
730     # from what Ganeti believes it should be. Different hypervisors may have
731     # different requirements, so we should probably review the design of
732     # storing it altogether, for the next major version.
733     if ((instance.hvm_disk_type is None) or
734         (instance.hvm_disk_type == constants.HT_HVM_DEV_IOEMU)):
735       disk_type = "ioemu:"
736     else:
737       disk_type = ""
738
739     disk_data = ["'phy:%s,%s,w'" %
740                  (dev_path, iv_name.replace("sd", "%shd" % disk_type))
741                  for dev_path, iv_name in block_devices]
742
743     if instance.hvm_cdrom_image_path is None:
744       config.write("disk = [%s]\n" % (",".join(disk_data)))
745     else:
746       iso = "'file:%s,hdc:cdrom,r'" % (instance.hvm_cdrom_image_path)
747       config.write("disk = [%s, %s]\n" % (",".join(disk_data), iso))
748
749     config.write("on_poweroff = 'destroy'\n")
750     config.write("on_reboot = 'restart'\n")
751     config.write("on_crash = 'restart'\n")
752     if extra_args:
753       config.write("extra = '%s'\n" % extra_args)
754     # just in case it exists
755     utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
756     try:
757       f = open("/etc/xen/%s" % instance.name, "w")
758       try:
759         f.write(config.getvalue())
760       finally:
761         f.close()
762     except IOError, err:
763       raise errors.OpExecError("Cannot write Xen instance confile"
764                                " file /etc/xen/%s: %s" % (instance.name, err))
765     return True