Fixes to pass pep8 (make lint)
[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     # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
545     #        This should be reworked in Ganeti 2.7
546     #  ssh must recognize the key of the target host for the migration
547     args = [constants.XEN_CMD, "migrate"]
548     if constants.XEN_CMD == constants.XEN_CMD_XM:
549       args.extend(["-p", "%d" % port])
550       if live:
551         args.append("-l")
552     elif constants.XEN_CMD == constants.XEN_CMD_XL:
553       cluster_name = ssconf.SimpleStore().GetClusterName()
554       args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
555       args.extend(["-C", self._ConfigFileName(instance.name)])
556     else:
557       raise errors.HypervisorError("Unsupported xen command: %s" %
558                                    constants.XEN_CMD)
559
560     args.extend([instance.name, target])
561     result = utils.RunCmd(args)
562     if result.failed:
563       raise errors.HypervisorError("Failed to migrate instance %s: %s" %
564                                    (instance.name, result.output))
565
566   def FinalizeMigrationSource(self, instance, success, live):
567     """Finalize the instance migration on the source node.
568
569     @type instance: L{objects.Instance}
570     @param instance: the instance that was migrated
571     @type success: bool
572     @param success: whether the migration succeeded or not
573     @type live: bool
574     @param live: whether the user requested a live migration or not
575
576     """
577     # pylint: disable=W0613
578     if success:
579       # remove old xen file after migration succeeded
580       try:
581         self._RemoveConfigFile(instance.name)
582       except EnvironmentError:
583         logging.exception("Failure while removing instance config file")
584
585   def GetMigrationStatus(self, instance):
586     """Get the migration status
587
588     As MigrateInstance for Xen is still blocking, if this method is called it
589     means that MigrateInstance has completed successfully. So we can safely
590     assume that the migration was successful and notify this fact to the client.
591
592     @type instance: L{objects.Instance}
593     @param instance: the instance that is being migrated
594     @rtype: L{objects.MigrationStatus}
595     @return: the status of the current migration (one of
596              L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
597              progress info that can be retrieved from the hypervisor
598
599     """
600     return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
601
602   @classmethod
603   def PowercycleNode(cls):
604     """Xen-specific powercycle.
605
606     This first does a Linux reboot (which triggers automatically a Xen
607     reboot), and if that fails it tries to do a Xen reboot. The reason
608     we don't try a Xen reboot first is that the xen reboot launches an
609     external command which connects to the Xen hypervisor, and that
610     won't work in case the root filesystem is broken and/or the xend
611     daemon is not working.
612
613     """
614     try:
615       cls.LinuxPowercycle()
616     finally:
617       utils.RunCmd([constants.XEN_CMD, "debug", "R"])
618
619
620 class XenPvmHypervisor(XenHypervisor):
621   """Xen PVM hypervisor interface"""
622
623   PARAMETERS = {
624     constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
625     constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
626     constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
627     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
628     constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
629     constants.HV_ROOT_PATH: hv_base.NO_CHECK,
630     constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
631     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
632     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
633     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
634     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
635     constants.HV_REBOOT_BEHAVIOR:
636       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
637     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
638     constants.HV_CPU_CAP: hv_base.NO_CHECK,
639     constants.HV_CPU_WEIGHT:
640       (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
641     }
642
643   @classmethod
644   def _WriteConfigFile(cls, instance, startup_memory, block_devices):
645     """Write the Xen config file for the instance.
646
647     """
648     hvp = instance.hvparams
649     config = StringIO()
650     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
651
652     # if bootloader is True, use bootloader instead of kernel and ramdisk
653     # parameters.
654     if hvp[constants.HV_USE_BOOTLOADER]:
655       # bootloader handling
656       bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
657       if bootloader_path:
658         config.write("bootloader = '%s'\n" % bootloader_path)
659       else:
660         raise errors.HypervisorError("Bootloader enabled, but missing"
661                                      " bootloader path")
662
663       bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
664       if bootloader_args:
665         config.write("bootargs = '%s'\n" % bootloader_args)
666     else:
667       # kernel handling
668       kpath = hvp[constants.HV_KERNEL_PATH]
669       config.write("kernel = '%s'\n" % kpath)
670
671       # initrd handling
672       initrd_path = hvp[constants.HV_INITRD_PATH]
673       if initrd_path:
674         config.write("ramdisk = '%s'\n" % initrd_path)
675
676     # rest of the settings
677     config.write("memory = %d\n" % startup_memory)
678     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
679     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
680     cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
681     if cpu_pinning:
682       config.write("%s\n" % cpu_pinning)
683     cpu_cap = hvp[constants.HV_CPU_CAP]
684     if cpu_cap:
685       config.write("cpu_cap=%d\n" % cpu_cap)
686     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
687     if cpu_weight:
688       config.write("cpu_weight=%d\n" % cpu_weight)
689
690     config.write("name = '%s'\n" % instance.name)
691
692     vif_data = []
693     for nic in instance.nics:
694       nic_str = "mac=%s" % (nic.mac)
695       ip = getattr(nic, "ip", None)
696       if ip is not None:
697         nic_str += ", ip=%s" % ip
698       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
699         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
700       vif_data.append("'%s'" % nic_str)
701
702     disk_data = cls._GetConfigFileDiskData(block_devices,
703                                            hvp[constants.HV_BLOCKDEV_PREFIX])
704
705     config.write("vif = [%s]\n" % ",".join(vif_data))
706     config.write("disk = [%s]\n" % ",".join(disk_data))
707
708     if hvp[constants.HV_ROOT_PATH]:
709       config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
710     config.write("on_poweroff = 'destroy'\n")
711     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
712       config.write("on_reboot = 'restart'\n")
713     else:
714       config.write("on_reboot = 'destroy'\n")
715     config.write("on_crash = 'restart'\n")
716     config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
717     cls._WriteConfigFileStatic(instance.name, config.getvalue())
718
719     return True
720
721
722 class XenHvmHypervisor(XenHypervisor):
723   """Xen HVM hypervisor interface"""
724
725   ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
726     pathutils.VNC_PASSWORD_FILE,
727     ]
728   ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
729     pathutils.VNC_PASSWORD_FILE,
730     ]
731
732   PARAMETERS = {
733     constants.HV_ACPI: hv_base.NO_CHECK,
734     constants.HV_BOOT_ORDER: (True, ) +
735       (lambda x: x and len(x.strip("acdn")) == 0,
736        "Invalid boot order specified, must be one or more of [acdn]",
737        None, None),
738     constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
739     constants.HV_DISK_TYPE:
740       hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
741     constants.HV_NIC_TYPE:
742       hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
743     constants.HV_PAE: hv_base.NO_CHECK,
744     constants.HV_VNC_BIND_ADDRESS:
745       (False, netutils.IP4Address.IsValid,
746        "VNC bind address is not a valid IP address", None, None),
747     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
748     constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
749     constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
750     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
751     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
752     constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
753     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
754     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
755     # Add PCI passthrough
756     constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
757     constants.HV_REBOOT_BEHAVIOR:
758       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
759     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
760     constants.HV_CPU_CAP: hv_base.NO_CHECK,
761     constants.HV_CPU_WEIGHT:
762       (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
763     }
764
765   @classmethod
766   def _WriteConfigFile(cls, instance, startup_memory, block_devices):
767     """Create a Xen 3.1 HVM config file.
768
769     """
770     hvp = instance.hvparams
771
772     config = StringIO()
773     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
774
775     # kernel handling
776     kpath = hvp[constants.HV_KERNEL_PATH]
777     config.write("kernel = '%s'\n" % kpath)
778
779     config.write("builder = 'hvm'\n")
780     config.write("memory = %d\n" % startup_memory)
781     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
782     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
783     cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
784     if cpu_pinning:
785       config.write("%s\n" % cpu_pinning)
786     cpu_cap = hvp[constants.HV_CPU_CAP]
787     if cpu_cap:
788       config.write("cpu_cap=%d\n" % cpu_cap)
789     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
790     if cpu_weight:
791       config.write("cpu_weight=%d\n" % cpu_weight)
792
793     config.write("name = '%s'\n" % instance.name)
794     if hvp[constants.HV_PAE]:
795       config.write("pae = 1\n")
796     else:
797       config.write("pae = 0\n")
798     if hvp[constants.HV_ACPI]:
799       config.write("acpi = 1\n")
800     else:
801       config.write("acpi = 0\n")
802     config.write("apic = 1\n")
803     config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
804     config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
805     config.write("sdl = 0\n")
806     config.write("usb = 1\n")
807     config.write("usbdevice = 'tablet'\n")
808     config.write("vnc = 1\n")
809     if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
810       config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
811     else:
812       config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
813
814     if instance.network_port > constants.VNC_BASE_PORT:
815       display = instance.network_port - constants.VNC_BASE_PORT
816       config.write("vncdisplay = %s\n" % display)
817       config.write("vncunused = 0\n")
818     else:
819       config.write("# vncdisplay = 1\n")
820       config.write("vncunused = 1\n")
821
822     vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
823     try:
824       password = utils.ReadFile(vnc_pwd_file)
825     except EnvironmentError, err:
826       raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
827                                    (vnc_pwd_file, err))
828
829     config.write("vncpasswd = '%s'\n" % password.rstrip())
830
831     config.write("serial = 'pty'\n")
832     if hvp[constants.HV_USE_LOCALTIME]:
833       config.write("localtime = 1\n")
834
835     vif_data = []
836     nic_type = hvp[constants.HV_NIC_TYPE]
837     if nic_type is None:
838       # ensure old instances don't change
839       nic_type_str = ", type=ioemu"
840     elif nic_type == constants.HT_NIC_PARAVIRTUAL:
841       nic_type_str = ", type=paravirtualized"
842     else:
843       nic_type_str = ", model=%s, type=ioemu" % nic_type
844     for nic in instance.nics:
845       nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
846       ip = getattr(nic, "ip", None)
847       if ip is not None:
848         nic_str += ", ip=%s" % ip
849       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
850         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
851       vif_data.append("'%s'" % nic_str)
852
853     config.write("vif = [%s]\n" % ",".join(vif_data))
854
855     disk_data = cls._GetConfigFileDiskData(block_devices,
856                                            hvp[constants.HV_BLOCKDEV_PREFIX])
857
858     iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
859     if iso_path:
860       iso = "'file:%s,hdc:cdrom,r'" % iso_path
861       disk_data.append(iso)
862
863     config.write("disk = [%s]\n" % (",".join(disk_data)))
864     # Add PCI passthrough
865     pci_pass_arr = []
866     pci_pass = hvp[constants.HV_PASSTHROUGH]
867     if pci_pass:
868       pci_pass_arr = pci_pass.split(";")
869       config.write("pci = %s\n" % pci_pass_arr)
870     config.write("on_poweroff = 'destroy'\n")
871     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
872       config.write("on_reboot = 'restart'\n")
873     else:
874       config.write("on_reboot = 'destroy'\n")
875     config.write("on_crash = 'restart'\n")
876     cls._WriteConfigFileStatic(instance.name, config.getvalue())
877
878     return True