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