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