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