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