hv_xen: Prepare for unit tests, remove {static,class}method
[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 _WriteConfigFile(cls, instance, startup_memory, block_devices):
344     """Write the Xen config file for the instance.
345
346     """
347     raise NotImplementedError
348
349   def _WriteConfigFileStatic(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 StartInstance(self, instance, block_devices, startup_paused):
427     """Start an instance.
428
429     """
430     startup_memory = self._InstanceStartupMemory(instance)
431     self._WriteConfigFile(instance, startup_memory, block_devices)
432     cmd = [constants.XEN_CMD, "create"]
433     if startup_paused:
434       cmd.extend(["-p"])
435     cmd.extend([self._ConfigFileName(instance.name)])
436     result = utils.RunCmd(cmd)
437
438     if result.failed:
439       raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
440                                    (instance.name, result.fail_reason,
441                                     result.output))
442
443   def StopInstance(self, instance, force=False, retry=False, name=None):
444     """Stop an instance.
445
446     """
447     if name is None:
448       name = instance.name
449     self._RemoveConfigFile(name)
450     if force:
451       command = [constants.XEN_CMD, "destroy", name]
452     else:
453       command = [constants.XEN_CMD, "shutdown", name]
454     result = utils.RunCmd(command)
455
456     if result.failed:
457       raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
458                                    (name, result.fail_reason, result.output))
459
460   def RebootInstance(self, instance):
461     """Reboot an instance.
462
463     """
464     ini_info = self.GetInstanceInfo(instance.name)
465
466     if ini_info is None:
467       raise errors.HypervisorError("Failed to reboot instance %s,"
468                                    " not running" % instance.name)
469
470     result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
471     if result.failed:
472       raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
473                                    (instance.name, result.fail_reason,
474                                     result.output))
475
476     def _CheckInstance():
477       new_info = self.GetInstanceInfo(instance.name)
478
479       # check if the domain ID has changed or the run time has decreased
480       if (new_info is not None and
481           (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
482         return
483
484       raise utils.RetryAgain()
485
486     try:
487       utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
488                   self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
489     except utils.RetryTimeout:
490       raise errors.HypervisorError("Failed to reboot instance %s: instance"
491                                    " did not reboot in the expected interval" %
492                                    (instance.name, ))
493
494   def BalloonInstanceMemory(self, instance, mem):
495     """Balloon an instance memory to a certain value.
496
497     @type instance: L{objects.Instance}
498     @param instance: instance to be accepted
499     @type mem: int
500     @param mem: actual memory size to use for instance runtime
501
502     """
503     cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
504     result = utils.RunCmd(cmd)
505     if result.failed:
506       raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
507                                    (instance.name, result.fail_reason,
508                                     result.output))
509     cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
510     cmd.append(self._ConfigFileName(instance.name))
511     result = utils.RunCmd(cmd)
512     if result.failed:
513       raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
514                                    (instance.name, result.fail_reason,
515                                     result.output))
516
517   def GetNodeInfo(self):
518     """Return information about the node.
519
520     @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
521
522     """
523     # TODO: Abstract running Xen command for testing
524     result = utils.RunCmd([constants.XEN_CMD, "info"])
525     if result.failed:
526       logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
527                     result.output)
528       return None
529
530     return _GetNodeInfo(result.stdout, self._GetXmList)
531
532   @classmethod
533   def GetInstanceConsole(cls, instance, hvparams, beparams):
534     """Return a command for connecting to the console of an instance.
535
536     """
537     return objects.InstanceConsole(instance=instance.name,
538                                    kind=constants.CONS_SSH,
539                                    host=instance.primary_node,
540                                    user=constants.SSH_CONSOLE_USER,
541                                    command=[pathutils.XEN_CONSOLE_WRAPPER,
542                                             constants.XEN_CMD, instance.name])
543
544   def Verify(self):
545     """Verify the hypervisor.
546
547     For Xen, this verifies that the xend process is running.
548
549     @return: Problem description if something is wrong, C{None} otherwise
550
551     """
552     result = utils.RunCmd([constants.XEN_CMD, "info"])
553     if result.failed:
554       return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
555
556     return None
557
558   def MigrationInfo(self, instance):
559     """Get instance information to perform a migration.
560
561     @type instance: L{objects.Instance}
562     @param instance: instance to be migrated
563     @rtype: string
564     @return: content of the xen config file
565
566     """
567     return self._ReadConfigFile(instance.name)
568
569   def AcceptInstance(self, instance, info, target):
570     """Prepare to accept an instance.
571
572     @type instance: L{objects.Instance}
573     @param instance: instance to be accepted
574     @type info: string
575     @param info: content of the xen config file on the source node
576     @type target: string
577     @param target: target host (usually ip), on this node
578
579     """
580     pass
581
582   def FinalizeMigrationDst(self, instance, info, success):
583     """Finalize an instance migration.
584
585     After a successful migration we write the xen config file.
586     We do nothing on a failure, as we did not change anything at accept time.
587
588     @type instance: L{objects.Instance}
589     @param instance: instance whose migration is being finalized
590     @type info: string
591     @param info: content of the xen config file on the source node
592     @type success: boolean
593     @param success: whether the migration was a success or a failure
594
595     """
596     if success:
597       self._WriteConfigFileStatic(instance.name, info)
598
599   def MigrateInstance(self, instance, target, live):
600     """Migrate an instance to a target node.
601
602     The migration will not be attempted if the instance is not
603     currently running.
604
605     @type instance: L{objects.Instance}
606     @param instance: the instance to be migrated
607     @type target: string
608     @param target: ip address of the target node
609     @type live: boolean
610     @param live: perform a live migration
611
612     """
613     if self.GetInstanceInfo(instance.name) is None:
614       raise errors.HypervisorError("Instance not running, cannot migrate")
615
616     port = instance.hvparams[constants.HV_MIGRATION_PORT]
617
618     if (constants.XEN_CMD == constants.XEN_CMD_XM and
619         not netutils.TcpPing(target, port, live_port_needed=True)):
620       raise errors.HypervisorError("Remote host %s not listening on port"
621                                    " %s, cannot migrate" % (target, port))
622
623     args = [constants.XEN_CMD, "migrate"]
624     if constants.XEN_CMD == constants.XEN_CMD_XM:
625       args.extend(["-p", "%d" % port])
626       if live:
627         args.append("-l")
628     elif constants.XEN_CMD == constants.XEN_CMD_XL:
629       cluster_name = ssconf.SimpleStore().GetClusterName()
630       args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
631       args.extend(["-C", self._ConfigFileName(instance.name)])
632     else:
633       raise errors.HypervisorError("Unsupported xen command: %s" %
634                                    constants.XEN_CMD)
635
636     args.extend([instance.name, target])
637     result = utils.RunCmd(args)
638     if result.failed:
639       raise errors.HypervisorError("Failed to migrate instance %s: %s" %
640                                    (instance.name, result.output))
641
642   def FinalizeMigrationSource(self, instance, success, live):
643     """Finalize the instance migration on the source node.
644
645     @type instance: L{objects.Instance}
646     @param instance: the instance that was migrated
647     @type success: bool
648     @param success: whether the migration succeeded or not
649     @type live: bool
650     @param live: whether the user requested a live migration or not
651
652     """
653     # pylint: disable=W0613
654     if success:
655       # remove old xen file after migration succeeded
656       try:
657         self._RemoveConfigFile(instance.name)
658       except EnvironmentError:
659         logging.exception("Failure while removing instance config file")
660
661   def GetMigrationStatus(self, instance):
662     """Get the migration status
663
664     As MigrateInstance for Xen is still blocking, if this method is called it
665     means that MigrateInstance has completed successfully. So we can safely
666     assume that the migration was successful and notify this fact to the client.
667
668     @type instance: L{objects.Instance}
669     @param instance: the instance that is being migrated
670     @rtype: L{objects.MigrationStatus}
671     @return: the status of the current migration (one of
672              L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
673              progress info that can be retrieved from the hypervisor
674
675     """
676     return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
677
678   @classmethod
679   def PowercycleNode(cls):
680     """Xen-specific powercycle.
681
682     This first does a Linux reboot (which triggers automatically a Xen
683     reboot), and if that fails it tries to do a Xen reboot. The reason
684     we don't try a Xen reboot first is that the xen reboot launches an
685     external command which connects to the Xen hypervisor, and that
686     won't work in case the root filesystem is broken and/or the xend
687     daemon is not working.
688
689     """
690     try:
691       cls.LinuxPowercycle()
692     finally:
693       utils.RunCmd([constants.XEN_CMD, "debug", "R"])
694
695
696 class XenPvmHypervisor(XenHypervisor):
697   """Xen PVM hypervisor interface"""
698
699   PARAMETERS = {
700     constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
701     constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
702     constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
703     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
704     constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
705     constants.HV_ROOT_PATH: hv_base.NO_CHECK,
706     constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
707     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
708     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
709     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
710     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
711     constants.HV_REBOOT_BEHAVIOR:
712       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
713     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
714     constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
715     constants.HV_CPU_WEIGHT:
716       (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
717     }
718
719   def _WriteConfigFile(self, instance, startup_memory, block_devices):
720     """Write the Xen config file for the instance.
721
722     """
723     hvp = instance.hvparams
724     config = StringIO()
725     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
726
727     # if bootloader is True, use bootloader instead of kernel and ramdisk
728     # parameters.
729     if hvp[constants.HV_USE_BOOTLOADER]:
730       # bootloader handling
731       bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
732       if bootloader_path:
733         config.write("bootloader = '%s'\n" % bootloader_path)
734       else:
735         raise errors.HypervisorError("Bootloader enabled, but missing"
736                                      " bootloader path")
737
738       bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
739       if bootloader_args:
740         config.write("bootargs = '%s'\n" % bootloader_args)
741     else:
742       # kernel handling
743       kpath = hvp[constants.HV_KERNEL_PATH]
744       config.write("kernel = '%s'\n" % kpath)
745
746       # initrd handling
747       initrd_path = hvp[constants.HV_INITRD_PATH]
748       if initrd_path:
749         config.write("ramdisk = '%s'\n" % initrd_path)
750
751     # rest of the settings
752     config.write("memory = %d\n" % startup_memory)
753     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
754     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
755     cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
756     if cpu_pinning:
757       config.write("%s\n" % cpu_pinning)
758     cpu_cap = hvp[constants.HV_CPU_CAP]
759     if cpu_cap:
760       config.write("cpu_cap=%d\n" % cpu_cap)
761     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
762     if cpu_weight:
763       config.write("cpu_weight=%d\n" % cpu_weight)
764
765     config.write("name = '%s'\n" % instance.name)
766
767     vif_data = []
768     for nic in instance.nics:
769       nic_str = "mac=%s" % (nic.mac)
770       ip = getattr(nic, "ip", None)
771       if ip is not None:
772         nic_str += ", ip=%s" % ip
773       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
774         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
775       vif_data.append("'%s'" % nic_str)
776
777     disk_data = \
778       _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
779
780     config.write("vif = [%s]\n" % ",".join(vif_data))
781     config.write("disk = [%s]\n" % ",".join(disk_data))
782
783     if hvp[constants.HV_ROOT_PATH]:
784       config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
785     config.write("on_poweroff = 'destroy'\n")
786     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
787       config.write("on_reboot = 'restart'\n")
788     else:
789       config.write("on_reboot = 'destroy'\n")
790     config.write("on_crash = 'restart'\n")
791     config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
792     self._WriteConfigFileStatic(instance.name, config.getvalue())
793
794     return True
795
796
797 class XenHvmHypervisor(XenHypervisor):
798   """Xen HVM hypervisor interface"""
799
800   ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
801     pathutils.VNC_PASSWORD_FILE,
802     ]
803   ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
804     pathutils.VNC_PASSWORD_FILE,
805     ]
806
807   PARAMETERS = {
808     constants.HV_ACPI: hv_base.NO_CHECK,
809     constants.HV_BOOT_ORDER: (True, ) +
810       (lambda x: x and len(x.strip("acdn")) == 0,
811        "Invalid boot order specified, must be one or more of [acdn]",
812        None, None),
813     constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
814     constants.HV_DISK_TYPE:
815       hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
816     constants.HV_NIC_TYPE:
817       hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
818     constants.HV_PAE: hv_base.NO_CHECK,
819     constants.HV_VNC_BIND_ADDRESS:
820       (False, netutils.IP4Address.IsValid,
821        "VNC bind address is not a valid IP address", None, None),
822     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
823     constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
824     constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
825     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
826     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
827     constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
828     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
829     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
830     # Add PCI passthrough
831     constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
832     constants.HV_REBOOT_BEHAVIOR:
833       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
834     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
835     constants.HV_CPU_CAP: hv_base.NO_CHECK,
836     constants.HV_CPU_WEIGHT:
837       (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
838     }
839
840   def _WriteConfigFile(self, instance, startup_memory, block_devices):
841     """Create a Xen 3.1 HVM config file.
842
843     """
844     hvp = instance.hvparams
845
846     config = StringIO()
847     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
848
849     # kernel handling
850     kpath = hvp[constants.HV_KERNEL_PATH]
851     config.write("kernel = '%s'\n" % kpath)
852
853     config.write("builder = 'hvm'\n")
854     config.write("memory = %d\n" % startup_memory)
855     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
856     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
857     cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
858     if cpu_pinning:
859       config.write("%s\n" % cpu_pinning)
860     cpu_cap = hvp[constants.HV_CPU_CAP]
861     if cpu_cap:
862       config.write("cpu_cap=%d\n" % cpu_cap)
863     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
864     if cpu_weight:
865       config.write("cpu_weight=%d\n" % cpu_weight)
866
867     config.write("name = '%s'\n" % instance.name)
868     if hvp[constants.HV_PAE]:
869       config.write("pae = 1\n")
870     else:
871       config.write("pae = 0\n")
872     if hvp[constants.HV_ACPI]:
873       config.write("acpi = 1\n")
874     else:
875       config.write("acpi = 0\n")
876     config.write("apic = 1\n")
877     config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
878     config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
879     config.write("sdl = 0\n")
880     config.write("usb = 1\n")
881     config.write("usbdevice = 'tablet'\n")
882     config.write("vnc = 1\n")
883     if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
884       config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
885     else:
886       config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
887
888     if instance.network_port > constants.VNC_BASE_PORT:
889       display = instance.network_port - constants.VNC_BASE_PORT
890       config.write("vncdisplay = %s\n" % display)
891       config.write("vncunused = 0\n")
892     else:
893       config.write("# vncdisplay = 1\n")
894       config.write("vncunused = 1\n")
895
896     vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
897     try:
898       password = utils.ReadFile(vnc_pwd_file)
899     except EnvironmentError, err:
900       raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
901                                    (vnc_pwd_file, err))
902
903     config.write("vncpasswd = '%s'\n" % password.rstrip())
904
905     config.write("serial = 'pty'\n")
906     if hvp[constants.HV_USE_LOCALTIME]:
907       config.write("localtime = 1\n")
908
909     vif_data = []
910     nic_type = hvp[constants.HV_NIC_TYPE]
911     if nic_type is None:
912       # ensure old instances don't change
913       nic_type_str = ", type=ioemu"
914     elif nic_type == constants.HT_NIC_PARAVIRTUAL:
915       nic_type_str = ", type=paravirtualized"
916     else:
917       nic_type_str = ", model=%s, type=ioemu" % nic_type
918     for nic in instance.nics:
919       nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
920       ip = getattr(nic, "ip", None)
921       if ip is not None:
922         nic_str += ", ip=%s" % ip
923       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
924         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
925       vif_data.append("'%s'" % nic_str)
926
927     config.write("vif = [%s]\n" % ",".join(vif_data))
928
929     disk_data = \
930       _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
931
932     iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
933     if iso_path:
934       iso = "'file:%s,hdc:cdrom,r'" % iso_path
935       disk_data.append(iso)
936
937     config.write("disk = [%s]\n" % (",".join(disk_data)))
938     # Add PCI passthrough
939     pci_pass_arr = []
940     pci_pass = hvp[constants.HV_PASSTHROUGH]
941     if pci_pass:
942       pci_pass_arr = pci_pass.split(";")
943       config.write("pci = %s\n" % pci_pass_arr)
944     config.write("on_poweroff = 'destroy'\n")
945     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
946       config.write("on_reboot = 'restart'\n")
947     else:
948       config.write("on_reboot = 'destroy'\n")
949     config.write("on_crash = 'restart'\n")
950     self._WriteConfigFileStatic(instance.name, config.getvalue())
951
952     return True