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