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