Merge branch 'devel-2.6' into submit
[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 vcluster
37 from ganeti import ssconf
38
39
40 XEND_CONFIG_FILE = vcluster.AddNodePrefix("/etc/xen/xend-config.sxp")
41 XL_CONFIG_FILE = vcluster.AddNodePrefix("/etc/xen/xl.conf")
42 VIF_BRIDGE_SCRIPT = vcluster.AddNodePrefix("/etc/xen/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 "/etc/xen/%s" % 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("/etc/xen/auto/%s" % instance_name)
94     cfg_file = XenHypervisor._ConfigFileName(instance_name)
95     try:
96       utils.WriteFile(cfg_file, data=data)
97     except EnvironmentError, err:
98       raise errors.HypervisorError("Cannot write Xen instance configuration"
99                                    " file %s: %s" % (cfg_file, err))
100
101   @staticmethod
102   def _ReadConfigFile(instance_name):
103     """Returns the contents of the instance config file.
104
105     """
106     try:
107       file_content = utils.ReadFile(
108                        XenHypervisor._ConfigFileName(instance_name))
109     except EnvironmentError, err:
110       raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
111     return file_content
112
113   @staticmethod
114   def _RemoveConfigFile(instance_name):
115     """Remove the xen configuration file.
116
117     """
118     utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
119
120   @classmethod
121   def _CreateConfigCpus(cls, cpu_mask):
122     """Create a CPU config string that's compatible with Xen's
123     configuration file.
124
125     """
126     # Convert the string CPU mask to a list of list of int's
127     cpu_list = utils.ParseMultiCpuMask(cpu_mask)
128
129     if len(cpu_list) == 1:
130       all_cpu_mapping = cpu_list[0]
131       if all_cpu_mapping == constants.CPU_PINNING_OFF:
132         # If CPU pinning has 1 entry that's "all", then remove the
133         # parameter from the config file
134         return None
135       else:
136         # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
137         # VM) to one physical CPU, using format 'cpu = "C"'
138         return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
139     else:
140       def _GetCPUMap(vcpu):
141         if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
142           cpu_map = constants.CPU_PINNING_ALL_XEN
143         else:
144           cpu_map = ",".join(map(str, vcpu))
145         return "\"%s\"" % cpu_map
146
147       # build the result string in format 'cpus = [ "c", "c", "c" ]',
148       # where each c is a physical CPU number, a range, a list, or any
149       # combination
150       return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
151
152   @staticmethod
153   def _RunXmList(xmlist_errors):
154     """Helper function for L{_GetXMList} to run "xm list".
155
156     """
157     result = utils.RunCmd([constants.XEN_CMD, "list"])
158     if result.failed:
159       logging.error("xm list failed (%s): %s", result.fail_reason,
160                     result.output)
161       xmlist_errors.append(result)
162       raise utils.RetryAgain()
163
164     # skip over the heading
165     return result.stdout.splitlines()[1:]
166
167   @classmethod
168   def _GetXMList(cls, include_node):
169     """Return the list of running instances.
170
171     If the include_node argument is True, then we return information
172     for dom0 also, otherwise we filter that from the return value.
173
174     @return: list of (name, id, memory, vcpus, state, time spent)
175
176     """
177     xmlist_errors = []
178     try:
179       lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
180     except utils.RetryTimeout:
181       if xmlist_errors:
182         xmlist_result = xmlist_errors.pop()
183
184         errmsg = ("xm list failed, timeout exceeded (%s): %s" %
185                   (xmlist_result.fail_reason, xmlist_result.output))
186       else:
187         errmsg = "xm list failed"
188
189       raise errors.HypervisorError(errmsg)
190
191     result = []
192     for line in lines:
193       # The format of lines is:
194       # Name      ID Mem(MiB) VCPUs State  Time(s)
195       # Domain-0   0  3418     4 r-----    266.2
196       data = line.split()
197       if len(data) != 6:
198         raise errors.HypervisorError("Can't parse output of xm list,"
199                                      " line: %s" % line)
200       try:
201         data[1] = int(data[1])
202         data[2] = int(data[2])
203         data[3] = int(data[3])
204         data[5] = float(data[5])
205       except (TypeError, ValueError), err:
206         raise errors.HypervisorError("Can't parse output of xm list,"
207                                      " line: %s, error: %s" % (line, err))
208
209       # skip the Domain-0 (optional)
210       if include_node or data[0] != _DOM0_NAME:
211         result.append(data)
212
213     return result
214
215   def ListInstances(self):
216     """Get the list of running instances.
217
218     """
219     xm_list = self._GetXMList(False)
220     names = [info[0] for info in xm_list]
221     return names
222
223   def GetInstanceInfo(self, instance_name):
224     """Get instance properties.
225
226     @param instance_name: the instance name
227
228     @return: tuple (name, id, memory, vcpus, stat, times)
229
230     """
231     xm_list = self._GetXMList(instance_name == _DOM0_NAME)
232     result = None
233     for data in xm_list:
234       if data[0] == instance_name:
235         result = data
236         break
237     return result
238
239   def GetAllInstancesInfo(self):
240     """Get properties of all instances.
241
242     @return: list of tuples (name, id, memory, vcpus, stat, times)
243
244     """
245     xm_list = self._GetXMList(False)
246     return xm_list
247
248   def StartInstance(self, instance, block_devices, startup_paused):
249     """Start an instance.
250
251     """
252     startup_memory = self._InstanceStartupMemory(instance)
253     self._WriteConfigFile(instance, startup_memory, block_devices)
254     cmd = [constants.XEN_CMD, "create"]
255     if startup_paused:
256       cmd.extend(["-p"])
257     cmd.extend([self._ConfigFileName(instance.name)])
258     result = utils.RunCmd(cmd)
259
260     if result.failed:
261       raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
262                                    (instance.name, result.fail_reason,
263                                     result.output))
264
265   def StopInstance(self, instance, force=False, retry=False, name=None):
266     """Stop an instance.
267
268     """
269     if name is None:
270       name = instance.name
271     self._RemoveConfigFile(name)
272     if force:
273       command = [constants.XEN_CMD, "destroy", name]
274     else:
275       command = [constants.XEN_CMD, "shutdown", name]
276     result = utils.RunCmd(command)
277
278     if result.failed:
279       raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
280                                    (name, result.fail_reason, result.output))
281
282   def RebootInstance(self, instance):
283     """Reboot an instance.
284
285     """
286     ini_info = self.GetInstanceInfo(instance.name)
287
288     if ini_info is None:
289       raise errors.HypervisorError("Failed to reboot instance %s,"
290                                    " not running" % instance.name)
291
292     result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
293     if result.failed:
294       raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
295                                    (instance.name, result.fail_reason,
296                                     result.output))
297
298     def _CheckInstance():
299       new_info = self.GetInstanceInfo(instance.name)
300
301       # check if the domain ID has changed or the run time has decreased
302       if (new_info is not None and
303           (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
304         return
305
306       raise utils.RetryAgain()
307
308     try:
309       utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
310                   self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
311     except utils.RetryTimeout:
312       raise errors.HypervisorError("Failed to reboot instance %s: instance"
313                                    " did not reboot in the expected interval" %
314                                    (instance.name, ))
315
316   def BalloonInstanceMemory(self, instance, mem):
317     """Balloon an instance memory to a certain value.
318
319     @type instance: L{objects.Instance}
320     @param instance: instance to be accepted
321     @type mem: int
322     @param mem: actual memory size to use for instance runtime
323
324     """
325     cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
326     result = utils.RunCmd(cmd)
327     if result.failed:
328       raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
329                                    (instance.name, result.fail_reason,
330                                     result.output))
331     cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
332     cmd.append(XenHypervisor._ConfigFileName(instance.name))
333     result = utils.RunCmd(cmd)
334     if result.failed:
335       raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
336                                    (instance.name, result.fail_reason,
337                                     result.output))
338
339   def GetNodeInfo(self):
340     """Return information about the node.
341
342     @return: a dict with the following keys (memory values in MiB):
343           - memory_total: the total memory size on the node
344           - memory_free: the available memory on the node for instances
345           - memory_dom0: the memory used by the node itself, if available
346           - nr_cpus: total number of CPUs
347           - nr_nodes: in a NUMA system, the number of domains
348           - nr_sockets: the number of physical CPU sockets in the node
349           - hv_version: the hypervisor version in the form (major, minor)
350
351     """
352     result = utils.RunCmd([constants.XEN_CMD, "info"])
353     if result.failed:
354       logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
355                     result.output)
356       return None
357
358     xmoutput = result.stdout.splitlines()
359     result = {}
360     cores_per_socket = threads_per_core = nr_cpus = None
361     xen_major, xen_minor = None, None
362     memory_total = None
363     memory_free = None
364
365     for line in xmoutput:
366       splitfields = line.split(":", 1)
367
368       if len(splitfields) > 1:
369         key = splitfields[0].strip()
370         val = splitfields[1].strip()
371
372         # note: in xen 3, memory has changed to total_memory
373         if key == "memory" or key == "total_memory":
374           memory_total = int(val)
375         elif key == "free_memory":
376           memory_free = int(val)
377         elif key == "nr_cpus":
378           nr_cpus = result["cpu_total"] = int(val)
379         elif key == "nr_nodes":
380           result["cpu_nodes"] = int(val)
381         elif key == "cores_per_socket":
382           cores_per_socket = int(val)
383         elif key == "threads_per_core":
384           threads_per_core = int(val)
385         elif key == "xen_major":
386           xen_major = int(val)
387         elif key == "xen_minor":
388           xen_minor = int(val)
389
390     if None not in [cores_per_socket, threads_per_core, nr_cpus]:
391       result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
392
393     total_instmem = 0
394     for (name, _, mem, vcpus, _, _) in self._GetXMList(True):
395       if name == _DOM0_NAME:
396         result["memory_dom0"] = mem
397         result["dom0_cpus"] = vcpus
398
399       # Include Dom0 in total memory usage
400       total_instmem += mem
401
402     if memory_free is not None:
403       result["memory_free"] = memory_free
404
405     if memory_total is not None:
406       result["memory_total"] = memory_total
407
408     # Calculate memory used by hypervisor
409     if None not in [memory_total, memory_free, total_instmem]:
410       result["memory_hv"] = memory_total - memory_free - total_instmem
411
412     if not (xen_major is None or xen_minor is None):
413       result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
414
415     return result
416
417   @classmethod
418   def GetInstanceConsole(cls, instance, hvparams, beparams):
419     """Return a command for connecting to the console of an instance.
420
421     """
422     return objects.InstanceConsole(instance=instance.name,
423                                    kind=constants.CONS_SSH,
424                                    host=instance.primary_node,
425                                    user=constants.SSH_CONSOLE_USER,
426                                    command=[pathutils.XEN_CONSOLE_WRAPPER,
427                                             constants.XEN_CMD, instance.name])
428
429   def Verify(self):
430     """Verify the hypervisor.
431
432     For Xen, this verifies that the xend process is running.
433
434     """
435     result = utils.RunCmd([constants.XEN_CMD, "info"])
436     if result.failed:
437       return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
438
439   @staticmethod
440   def _GetConfigFileDiskData(block_devices, blockdev_prefix):
441     """Get disk directive for xen config file.
442
443     This method builds the xen config disk directive according to the
444     given disk_template and block_devices.
445
446     @param block_devices: list of tuples (cfdev, rldev):
447         - cfdev: dict containing ganeti config disk part
448         - rldev: ganeti.bdev.BlockDev object
449     @param blockdev_prefix: a string containing blockdevice prefix,
450                             e.g. "sd" for /dev/sda
451
452     @return: string containing disk directive for xen instance config file
453
454     """
455     FILE_DRIVER_MAP = {
456       constants.FD_LOOP: "file",
457       constants.FD_BLKTAP: "tap:aio",
458       }
459     disk_data = []
460     if len(block_devices) > 24:
461       # 'z' - 'a' = 24
462       raise errors.HypervisorError("Too many disks")
463     namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
464     for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
465       if cfdev.mode == constants.DISK_RDWR:
466         mode = "w"
467       else:
468         mode = "r"
469       if cfdev.dev_type == constants.LD_FILE:
470         line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
471                                   dev_path, sd_name, mode)
472       else:
473         line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
474       disk_data.append(line)
475
476     return disk_data
477
478   def MigrationInfo(self, instance):
479     """Get instance information to perform a migration.
480
481     @type instance: L{objects.Instance}
482     @param instance: instance to be migrated
483     @rtype: string
484     @return: content of the xen config file
485
486     """
487     return self._ReadConfigFile(instance.name)
488
489   def AcceptInstance(self, instance, info, target):
490     """Prepare to accept an instance.
491
492     @type instance: L{objects.Instance}
493     @param instance: instance to be accepted
494     @type info: string
495     @param info: content of the xen config file on the source node
496     @type target: string
497     @param target: target host (usually ip), on this node
498
499     """
500     pass
501
502   def FinalizeMigrationDst(self, instance, info, success):
503     """Finalize an instance migration.
504
505     After a successful migration we write the xen config file.
506     We do nothing on a failure, as we did not change anything at accept time.
507
508     @type instance: L{objects.Instance}
509     @param instance: instance whose migration is being finalized
510     @type info: string
511     @param info: content of the xen config file on the source node
512     @type success: boolean
513     @param success: whether the migration was a success or a failure
514
515     """
516     if success:
517       self._WriteConfigFileStatic(instance.name, info)
518
519   def MigrateInstance(self, instance, target, live):
520     """Migrate an instance to a target node.
521
522     The migration will not be attempted if the instance is not
523     currently running.
524
525     @type instance: L{objects.Instance}
526     @param instance: the instance to be migrated
527     @type target: string
528     @param target: ip address of the target node
529     @type live: boolean
530     @param live: perform a live migration
531
532     """
533     if self.GetInstanceInfo(instance.name) is None:
534       raise errors.HypervisorError("Instance not running, cannot migrate")
535
536     port = instance.hvparams[constants.HV_MIGRATION_PORT]
537
538     if (constants.XEN_CMD == constants.XEN_CMD_XM and
539         not netutils.TcpPing(target, port, live_port_needed=True)):
540       raise errors.HypervisorError("Remote host %s not listening on port"
541                                    " %s, cannot migrate" % (target, port))
542
543     # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
544     #        This should be reworked in Ganeti 2.7
545     #  ssh must recognize the key of the target host for the migration
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.NO_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