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