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