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