Add cpuid support to XEN hypervisors
[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 errno
28 import string # pylint: disable=W0402
29 import shutil
30 from cStringIO import StringIO
31
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti.hypervisor import hv_base
36 from ganeti import netutils
37 from ganeti import objects
38 from ganeti import pathutils
39
40
41 XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
42 XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
43 VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
44                                    "scripts/vif-bridge")
45 _DOM0_NAME = "Domain-0"
46 _DISK_LETTERS = string.ascii_lowercase
47
48 _FILE_DRIVER_MAP = {
49   constants.FD_LOOP: "file",
50   constants.FD_BLKTAP: "tap:aio",
51   }
52
53
54 def _CreateConfigCpus(cpu_mask):
55   """Create a CPU config string for Xen's config file.
56
57   """
58   # Convert the string CPU mask to a list of list of int's
59   cpu_list = utils.ParseMultiCpuMask(cpu_mask)
60
61   if len(cpu_list) == 1:
62     all_cpu_mapping = cpu_list[0]
63     if all_cpu_mapping == constants.CPU_PINNING_OFF:
64       # If CPU pinning has 1 entry that's "all", then remove the
65       # parameter from the config file
66       return None
67     else:
68       # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
69       # VM) to one physical CPU, using format 'cpu = "C"'
70       return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
71   else:
72
73     def _GetCPUMap(vcpu):
74       if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
75         cpu_map = constants.CPU_PINNING_ALL_XEN
76       else:
77         cpu_map = ",".join(map(str, vcpu))
78       return "\"%s\"" % cpu_map
79
80     # build the result string in format 'cpus = [ "c", "c", "c" ]',
81     # where each c is a physical CPU number, a range, a list, or any
82     # combination
83     return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
84
85
86 def _RunInstanceList(fn, instance_list_errors):
87   """Helper function for L{_GetInstanceList} to retrieve the list of instances
88   from xen.
89
90   @type fn: callable
91   @param fn: Function to query xen for the list of instances
92   @type instance_list_errors: list
93   @param instance_list_errors: Error list
94   @rtype: list
95
96   """
97   result = fn()
98   if result.failed:
99     logging.error("Retrieving the instance list from xen failed (%s): %s",
100                   result.fail_reason, result.output)
101     instance_list_errors.append(result)
102     raise utils.RetryAgain()
103
104   # skip over the heading
105   return result.stdout.splitlines()
106
107
108 def _ParseInstanceList(lines, include_node):
109   """Parses the output of listing instances by xen.
110
111   @type lines: list
112   @param lines: Result of retrieving the instance list from xen
113   @type include_node: boolean
114   @param include_node: If True, return information for Dom0
115   @return: list of tuple containing (name, id, memory, vcpus, state, time
116     spent)
117
118   """
119   result = []
120
121   # Iterate through all lines while ignoring header
122   for line in lines[1:]:
123     # The format of lines is:
124     # Name      ID Mem(MiB) VCPUs State  Time(s)
125     # Domain-0   0  3418     4 r-----    266.2
126     data = line.split()
127     if len(data) != 6:
128       raise errors.HypervisorError("Can't parse instance list,"
129                                    " line: %s" % line)
130     try:
131       data[1] = int(data[1])
132       data[2] = int(data[2])
133       data[3] = int(data[3])
134       data[5] = float(data[5])
135     except (TypeError, ValueError), err:
136       raise errors.HypervisorError("Can't parse instance list,"
137                                    " line: %s, error: %s" % (line, err))
138
139     # skip the Domain-0 (optional)
140     if include_node or data[0] != _DOM0_NAME:
141       result.append(data)
142
143   return result
144
145
146 def _GetInstanceList(fn, include_node, _timeout=5):
147   """Return the list of running instances.
148
149   See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details.
150
151   """
152   instance_list_errors = []
153   try:
154     lines = utils.Retry(_RunInstanceList, (0.3, 1.5, 1.0), _timeout,
155                         args=(fn, instance_list_errors))
156   except utils.RetryTimeout:
157     if instance_list_errors:
158       instance_list_result = instance_list_errors.pop()
159
160       errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
161                 (instance_list_result.fail_reason, instance_list_result.output))
162     else:
163       errmsg = "listing instances failed"
164
165     raise errors.HypervisorError(errmsg)
166
167   return _ParseInstanceList(lines, include_node)
168
169
170 def _ParseNodeInfo(info):
171   """Return information about the node.
172
173   @return: a dict with the following keys (memory values in MiB):
174         - memory_total: the total memory size on the node
175         - memory_free: the available memory on the node for instances
176         - nr_cpus: total number of CPUs
177         - nr_nodes: in a NUMA system, the number of domains
178         - nr_sockets: the number of physical CPU sockets in the node
179         - hv_version: the hypervisor version in the form (major, minor)
180
181   """
182   result = {}
183   cores_per_socket = threads_per_core = nr_cpus = None
184   xen_major, xen_minor = None, None
185   memory_total = None
186   memory_free = None
187
188   for line in info.splitlines():
189     fields = line.split(":", 1)
190
191     if len(fields) < 2:
192       continue
193
194     (key, val) = map(lambda s: s.strip(), fields)
195
196     # Note: in Xen 3, memory has changed to total_memory
197     if key in ("memory", "total_memory"):
198       memory_total = int(val)
199     elif key == "free_memory":
200       memory_free = int(val)
201     elif key == "nr_cpus":
202       nr_cpus = result["cpu_total"] = int(val)
203     elif key == "nr_nodes":
204       result["cpu_nodes"] = int(val)
205     elif key == "cores_per_socket":
206       cores_per_socket = int(val)
207     elif key == "threads_per_core":
208       threads_per_core = int(val)
209     elif key == "xen_major":
210       xen_major = int(val)
211     elif key == "xen_minor":
212       xen_minor = int(val)
213
214   if None not in [cores_per_socket, threads_per_core, nr_cpus]:
215     result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
216
217   if memory_free is not None:
218     result["memory_free"] = memory_free
219
220   if memory_total is not None:
221     result["memory_total"] = memory_total
222
223   if not (xen_major is None or xen_minor is None):
224     result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
225
226   return result
227
228
229 def _MergeInstanceInfo(info, instance_list):
230   """Updates node information from L{_ParseNodeInfo} with instance info.
231
232   @type info: dict
233   @param info: Result from L{_ParseNodeInfo}
234   @type instance_list: list of tuples
235   @param instance_list: list of instance information; one tuple per instance
236   @rtype: dict
237
238   """
239   total_instmem = 0
240
241   for (name, _, mem, vcpus, _, _) in instance_list:
242     if name == _DOM0_NAME:
243       info["memory_dom0"] = mem
244       info["cpu_dom0"] = vcpus
245
246     # Include Dom0 in total memory usage
247     total_instmem += mem
248
249   memory_free = info.get("memory_free")
250   memory_total = info.get("memory_total")
251
252   # Calculate memory used by hypervisor
253   if None not in [memory_total, memory_free, total_instmem]:
254     info["memory_hv"] = memory_total - memory_free - total_instmem
255
256   return info
257
258
259 def _GetNodeInfo(info, instance_list):
260   """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
261
262   @type instance_list: list of tuples
263   @param instance_list: list of instance information; one tuple per instance
264
265   """
266   return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
267
268
269 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
270                            _letters=_DISK_LETTERS):
271   """Get disk directives for Xen config file.
272
273   This method builds the xen config disk directive according to the
274   given disk_template and block_devices.
275
276   @param block_devices: list of tuples (cfdev, rldev):
277       - cfdev: dict containing ganeti config disk part
278       - rldev: ganeti.block.bdev.BlockDev object
279   @param blockdev_prefix: a string containing blockdevice prefix,
280                           e.g. "sd" for /dev/sda
281
282   @return: string containing disk directive for xen instance config file
283
284   """
285   if len(block_devices) > len(_letters):
286     raise errors.HypervisorError("Too many disks")
287
288   disk_data = []
289
290   for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
291     sd_name = blockdev_prefix + sd_suffix
292
293     if cfdev.mode == constants.DISK_RDWR:
294       mode = "w"
295     else:
296       mode = "r"
297
298     if cfdev.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
299       driver = _FILE_DRIVER_MAP[cfdev.logical_id[0]]
300     else:
301       driver = "phy"
302
303     disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
304
305   return disk_data
306
307
308 def _QuoteCpuidField(data):
309   """Add quotes around the CPUID field only if necessary.
310
311   Xen CPUID fields come in two shapes: LIBXL strings, which need quotes around
312   them, and lists of XEND strings, which don't.
313
314   @param data: Either type of parameter.
315   @return: The quoted version thereof.
316
317   """
318   return "'%s'" % data if data.startswith("host") else data
319
320
321 class XenHypervisor(hv_base.BaseHypervisor):
322   """Xen generic hypervisor interface
323
324   This is the Xen base class used for both Xen PVM and HVM. It contains
325   all the functionality that is identical for both.
326
327   """
328   CAN_MIGRATE = True
329   REBOOT_RETRY_COUNT = 60
330   REBOOT_RETRY_INTERVAL = 10
331   _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
332   _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
333   _DIRS = [_ROOT_DIR, _NICS_DIR]
334
335   ANCILLARY_FILES = [
336     XEND_CONFIG_FILE,
337     XL_CONFIG_FILE,
338     VIF_BRIDGE_SCRIPT,
339     ]
340   ANCILLARY_FILES_OPT = [
341     XL_CONFIG_FILE,
342     ]
343
344   def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
345     hv_base.BaseHypervisor.__init__(self)
346
347     if _cfgdir is None:
348       self._cfgdir = pathutils.XEN_CONFIG_DIR
349     else:
350       self._cfgdir = _cfgdir
351
352     if _run_cmd_fn is None:
353       self._run_cmd_fn = utils.RunCmd
354     else:
355       self._run_cmd_fn = _run_cmd_fn
356
357     self._cmd = _cmd
358
359   def _GetCommand(self, hvparams):
360     """Returns Xen command to use.
361
362     @type hvparams: dict of strings
363     @param hvparams: hypervisor parameters
364
365     """
366     if self._cmd is None:
367       if hvparams is None or constants.HV_XEN_CMD not in hvparams:
368         raise errors.HypervisorError("Cannot determine xen command.")
369       else:
370         cmd = hvparams[constants.HV_XEN_CMD]
371     else:
372       cmd = self._cmd
373
374     if cmd not in constants.KNOWN_XEN_COMMANDS:
375       raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
376
377     return cmd
378
379   def _RunXen(self, args, hvparams):
380     """Wrapper around L{utils.process.RunCmd} to run Xen command.
381
382     @type hvparams: dict of strings
383     @param hvparams: dictionary of hypervisor params
384     @see: L{utils.process.RunCmd}
385
386     """
387     cmd = [self._GetCommand(hvparams)]
388     cmd.extend(args)
389
390     return self._run_cmd_fn(cmd)
391
392   def _ConfigFileName(self, instance_name):
393     """Get the config file name for an instance.
394
395     @param instance_name: instance name
396     @type instance_name: str
397     @return: fully qualified path to instance config file
398     @rtype: str
399
400     """
401     return utils.PathJoin(self._cfgdir, instance_name)
402
403   @classmethod
404   def _WriteNICInfoFile(cls, instance_name, idx, nic):
405     """Write the Xen config file for the instance.
406
407     This version of the function just writes the config file from static data.
408
409     """
410     dirs = [(dname, constants.RUN_DIRS_MODE)
411             for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
412     utils.EnsureDirs(dirs)
413
414     cfg_file = cls._InstanceNICFile(instance_name, idx)
415     data = StringIO()
416
417     if nic.netinfo:
418       netinfo = objects.Network.FromDict(nic.netinfo)
419       data.write("NETWORK_NAME=%s\n" % netinfo.name)
420       if netinfo.network:
421         data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
422       if netinfo.gateway:
423         data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
424       if netinfo.network6:
425         data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
426       if netinfo.gateway6:
427         data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
428       if netinfo.mac_prefix:
429         data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
430       if netinfo.tags:
431         data.write("NETWORK_TAGS=%s\n" % r"\ ".join(netinfo.tags))
432
433     data.write("MAC=%s\n" % nic.mac)
434     data.write("IP=%s\n" % nic.ip)
435     data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
436     data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
437
438     try:
439       utils.WriteFile(cfg_file, data=data.getvalue())
440     except EnvironmentError, err:
441       raise errors.HypervisorError("Cannot write Xen instance configuration"
442                                    " file %s: %s" % (cfg_file, err))
443
444   @classmethod
445   def _InstanceNICDir(cls, instance_name):
446     """Returns the directory holding the tap device files for a given instance.
447
448     """
449     return utils.PathJoin(cls._NICS_DIR, instance_name)
450
451   @classmethod
452   def _InstanceNICFile(cls, instance_name, seq):
453     """Returns the name of the file containing the tap device for a given NIC
454
455     """
456     return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
457
458   @classmethod
459   def _GetConfig(cls, instance, startup_memory, block_devices):
460     """Build Xen configuration for an instance.
461
462     """
463     raise NotImplementedError
464
465   def _WriteConfigFile(self, instance_name, data):
466     """Write the Xen config file for the instance.
467
468     This version of the function just writes the config file from static data.
469
470     """
471     # just in case it exists
472     utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
473
474     cfg_file = self._ConfigFileName(instance_name)
475     try:
476       utils.WriteFile(cfg_file, data=data)
477     except EnvironmentError, err:
478       raise errors.HypervisorError("Cannot write Xen instance configuration"
479                                    " file %s: %s" % (cfg_file, err))
480
481   def _ReadConfigFile(self, instance_name):
482     """Returns the contents of the instance config file.
483
484     """
485     filename = self._ConfigFileName(instance_name)
486
487     try:
488       file_content = utils.ReadFile(filename)
489     except EnvironmentError, err:
490       raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
491
492     return file_content
493
494   def _RemoveConfigFile(self, instance_name):
495     """Remove the xen configuration file.
496
497     """
498     utils.RemoveFile(self._ConfigFileName(instance_name))
499     try:
500       shutil.rmtree(self._InstanceNICDir(instance_name))
501     except OSError, err:
502       if err.errno != errno.ENOENT:
503         raise
504
505   def _StashConfigFile(self, instance_name):
506     """Move the Xen config file to the log directory and return its new path.
507
508     """
509     old_filename = self._ConfigFileName(instance_name)
510     base = ("%s-%s" %
511             (instance_name, utils.TimestampForFilename()))
512     new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
513     utils.RenameFile(old_filename, new_filename)
514     return new_filename
515
516   def _GetInstanceList(self, include_node, hvparams):
517     """Wrapper around module level L{_GetInstanceList}.
518
519     @type hvparams: dict of strings
520     @param hvparams: hypervisor parameters to be used on this node
521
522     """
523     return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
524                             include_node)
525
526   def ListInstances(self, hvparams=None):
527     """Get the list of running instances.
528
529     """
530     instance_list = self._GetInstanceList(False, hvparams)
531     names = [info[0] for info in instance_list]
532     return names
533
534   def GetInstanceInfo(self, instance_name, hvparams=None):
535     """Get instance properties.
536
537     @type instance_name: string
538     @param instance_name: the instance name
539     @type hvparams: dict of strings
540     @param hvparams: the instance's hypervisor params
541
542     @return: tuple (name, id, memory, vcpus, stat, times)
543
544     """
545     instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
546     result = None
547     for data in instance_list:
548       if data[0] == instance_name:
549         result = data
550         break
551     return result
552
553   def GetAllInstancesInfo(self, hvparams=None):
554     """Get properties of all instances.
555
556     @type hvparams: dict of strings
557     @param hvparams: hypervisor parameters
558     @return: list of tuples (name, id, memory, vcpus, stat, times)
559
560     """
561     return self._GetInstanceList(False, hvparams)
562
563   def _MakeConfigFile(self, instance, startup_memory, block_devices):
564     """Gather configuration details and write to disk.
565
566     See L{_GetConfig} for arguments.
567
568     """
569     buf = StringIO()
570     buf.write("# Automatically generated by Ganeti. Do not edit!\n")
571     buf.write("\n")
572     buf.write(self._GetConfig(instance, startup_memory, block_devices))
573     buf.write("\n")
574
575     self._WriteConfigFile(instance.name, buf.getvalue())
576
577   def StartInstance(self, instance, block_devices, startup_paused):
578     """Start an instance.
579
580     """
581     startup_memory = self._InstanceStartupMemory(instance,
582                                                  hvparams=instance.hvparams)
583
584     self._MakeConfigFile(instance, startup_memory, block_devices)
585
586     cmd = ["create"]
587     if startup_paused:
588       cmd.append("-p")
589     cmd.append(self._ConfigFileName(instance.name))
590
591     result = self._RunXen(cmd, instance.hvparams)
592     if result.failed:
593       # Move the Xen configuration file to the log directory to avoid
594       # leaving a stale config file behind.
595       stashed_config = self._StashConfigFile(instance.name)
596       raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
597                                    " config file to %s" %
598                                    (instance.name, result.fail_reason,
599                                     result.output, stashed_config))
600
601   def StopInstance(self, instance, force=False, retry=False, name=None):
602     """Stop an instance.
603
604     """
605     if name is None:
606       name = instance.name
607
608     return self._StopInstance(name, force, instance.hvparams)
609
610   def _StopInstance(self, name, force, hvparams):
611     """Stop an instance.
612
613     @type name: string
614     @param name: name of the instance to be shutdown
615     @type force: boolean
616     @param force: flag specifying whether shutdown should be forced
617     @type hvparams: dict of string
618     @param hvparams: hypervisor parameters of the instance
619
620     """
621     if force:
622       action = "destroy"
623     else:
624       action = "shutdown"
625
626     result = self._RunXen([action, name], hvparams)
627     if result.failed:
628       raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
629                                    (name, result.fail_reason, result.output))
630
631     # Remove configuration file if stopping/starting instance was successful
632     self._RemoveConfigFile(name)
633
634   def RebootInstance(self, instance):
635     """Reboot an instance.
636
637     """
638     ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
639
640     if ini_info is None:
641       raise errors.HypervisorError("Failed to reboot instance %s,"
642                                    " not running" % instance.name)
643
644     result = self._RunXen(["reboot", instance.name], instance.hvparams)
645     if result.failed:
646       raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
647                                    (instance.name, result.fail_reason,
648                                     result.output))
649
650     def _CheckInstance():
651       new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
652
653       # check if the domain ID has changed or the run time has decreased
654       if (new_info is not None and
655           (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
656         return
657
658       raise utils.RetryAgain()
659
660     try:
661       utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
662                   self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
663     except utils.RetryTimeout:
664       raise errors.HypervisorError("Failed to reboot instance %s: instance"
665                                    " did not reboot in the expected interval" %
666                                    (instance.name, ))
667
668   def BalloonInstanceMemory(self, instance, mem):
669     """Balloon an instance memory to a certain value.
670
671     @type instance: L{objects.Instance}
672     @param instance: instance to be accepted
673     @type mem: int
674     @param mem: actual memory size to use for instance runtime
675
676     """
677     result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
678     if result.failed:
679       raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
680                                    (instance.name, result.fail_reason,
681                                     result.output))
682
683     # Update configuration file
684     cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
685     cmd.append(self._ConfigFileName(instance.name))
686
687     result = utils.RunCmd(cmd)
688     if result.failed:
689       raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
690                                    (instance.name, result.fail_reason,
691                                     result.output))
692
693   def GetNodeInfo(self, hvparams=None):
694     """Return information about the node.
695
696     @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
697
698     """
699     result = self._RunXen(["info"], hvparams)
700     if result.failed:
701       logging.error("Can't retrieve xen hypervisor information (%s): %s",
702                     result.fail_reason, result.output)
703       return None
704
705     instance_list = self._GetInstanceList(True, hvparams)
706     return _GetNodeInfo(result.stdout, instance_list)
707
708   def GetInstanceConsole(self, instance, primary_node, hvparams, beparams):
709     """Return a command for connecting to the console of an instance.
710
711     """
712     xen_cmd = self._GetCommand(hvparams)
713     return objects.InstanceConsole(instance=instance.name,
714                                    kind=constants.CONS_SSH,
715                                    host=primary_node.name,
716                                    user=constants.SSH_CONSOLE_USER,
717                                    command=[pathutils.XEN_CONSOLE_WRAPPER,
718                                             xen_cmd, instance.name])
719
720   def Verify(self, hvparams=None):
721     """Verify the hypervisor.
722
723     For Xen, this verifies that the xend process is running.
724
725     @type hvparams: dict of strings
726     @param hvparams: hypervisor parameters to be verified against
727
728     @return: Problem description if something is wrong, C{None} otherwise
729
730     """
731     if hvparams is None:
732       return "Could not verify the hypervisor, because no hvparams were" \
733              " provided."
734
735     if constants.HV_XEN_CMD in hvparams:
736       xen_cmd = hvparams[constants.HV_XEN_CMD]
737       try:
738         self._CheckToolstack(xen_cmd)
739       except errors.HypervisorError:
740         return "The configured xen toolstack '%s' is not available on this" \
741                " node." % xen_cmd
742
743     result = self._RunXen(["info"], hvparams)
744     if result.failed:
745       return "Retrieving information from xen failed: %s, %s" % \
746         (result.fail_reason, result.output)
747
748     return None
749
750   def MigrationInfo(self, instance):
751     """Get instance information to perform a migration.
752
753     @type instance: L{objects.Instance}
754     @param instance: instance to be migrated
755     @rtype: string
756     @return: content of the xen config file
757
758     """
759     return self._ReadConfigFile(instance.name)
760
761   def AcceptInstance(self, instance, info, target):
762     """Prepare to accept an instance.
763
764     @type instance: L{objects.Instance}
765     @param instance: instance to be accepted
766     @type info: string
767     @param info: content of the xen config file on the source node
768     @type target: string
769     @param target: target host (usually ip), on this node
770
771     """
772     pass
773
774   def FinalizeMigrationDst(self, instance, info, success):
775     """Finalize an instance migration.
776
777     After a successful migration we write the xen config file.
778     We do nothing on a failure, as we did not change anything at accept time.
779
780     @type instance: L{objects.Instance}
781     @param instance: instance whose migration is being finalized
782     @type info: string
783     @param info: content of the xen config file on the source node
784     @type success: boolean
785     @param success: whether the migration was a success or a failure
786
787     """
788     if success:
789       self._WriteConfigFile(instance.name, info)
790
791   def MigrateInstance(self, cluster_name, instance, target, live):
792     """Migrate an instance to a target node.
793
794     The migration will not be attempted if the instance is not
795     currently running.
796
797     @type instance: L{objects.Instance}
798     @param instance: the instance to be migrated
799     @type target: string
800     @param target: ip address of the target node
801     @type live: boolean
802     @param live: perform a live migration
803
804     """
805     port = instance.hvparams[constants.HV_MIGRATION_PORT]
806
807     return self._MigrateInstance(cluster_name, instance.name, target, port,
808                                  live, instance.hvparams)
809
810   def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
811                        hvparams, _ping_fn=netutils.TcpPing):
812     """Migrate an instance to a target node.
813
814     @see: L{MigrateInstance} for details
815
816     """
817     if hvparams is None:
818       raise errors.HypervisorError("No hvparams provided.")
819
820     if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
821       raise errors.HypervisorError("Instance not running, cannot migrate")
822
823     cmd = self._GetCommand(hvparams)
824
825     if (cmd == constants.XEN_CMD_XM and
826         not _ping_fn(target, port, live_port_needed=True)):
827       raise errors.HypervisorError("Remote host %s not listening on port"
828                                    " %s, cannot migrate" % (target, port))
829
830     args = ["migrate"]
831
832     if cmd == constants.XEN_CMD_XM:
833       args.extend(["-p", "%d" % port])
834       if live:
835         args.append("-l")
836
837     elif cmd == constants.XEN_CMD_XL:
838       args.extend([
839         "-s", constants.XL_SSH_CMD % cluster_name,
840         "-C", self._ConfigFileName(instance_name),
841         ])
842
843     else:
844       raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
845
846     args.extend([instance_name, target])
847
848     result = self._RunXen(args, hvparams)
849     if result.failed:
850       raise errors.HypervisorError("Failed to migrate instance %s: %s" %
851                                    (instance_name, result.output))
852
853   def FinalizeMigrationSource(self, instance, success, live):
854     """Finalize the instance migration on the source node.
855
856     @type instance: L{objects.Instance}
857     @param instance: the instance that was migrated
858     @type success: bool
859     @param success: whether the migration succeeded or not
860     @type live: bool
861     @param live: whether the user requested a live migration or not
862
863     """
864     # pylint: disable=W0613
865     if success:
866       # remove old xen file after migration succeeded
867       try:
868         self._RemoveConfigFile(instance.name)
869       except EnvironmentError:
870         logging.exception("Failure while removing instance config file")
871
872   def GetMigrationStatus(self, instance):
873     """Get the migration status
874
875     As MigrateInstance for Xen is still blocking, if this method is called it
876     means that MigrateInstance has completed successfully. So we can safely
877     assume that the migration was successful and notify this fact to the client.
878
879     @type instance: L{objects.Instance}
880     @param instance: the instance that is being migrated
881     @rtype: L{objects.MigrationStatus}
882     @return: the status of the current migration (one of
883              L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
884              progress info that can be retrieved from the hypervisor
885
886     """
887     return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
888
889   def PowercycleNode(self, hvparams=None):
890     """Xen-specific powercycle.
891
892     This first does a Linux reboot (which triggers automatically a Xen
893     reboot), and if that fails it tries to do a Xen reboot. The reason
894     we don't try a Xen reboot first is that the xen reboot launches an
895     external command which connects to the Xen hypervisor, and that
896     won't work in case the root filesystem is broken and/or the xend
897     daemon is not working.
898
899     @type hvparams: dict of strings
900     @param hvparams: hypervisor params to be used on this node
901
902     """
903     try:
904       self.LinuxPowercycle()
905     finally:
906       xen_cmd = self._GetCommand(hvparams)
907       utils.RunCmd([xen_cmd, "debug", "R"])
908
909   def _CheckToolstack(self, xen_cmd):
910     """Check whether the given toolstack is available on the node.
911
912     @type xen_cmd: string
913     @param xen_cmd: xen command (e.g. 'xm' or 'xl')
914
915     """
916     binary_found = self._CheckToolstackBinary(xen_cmd)
917     if not binary_found:
918       raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
919     elif xen_cmd == constants.XEN_CMD_XL:
920       if not self._CheckToolstackXlConfigured():
921         raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
922                                      "node." % xen_cmd)
923
924   def _CheckToolstackBinary(self, xen_cmd):
925     """Checks whether the xen command's binary is found on the machine.
926
927     """
928     if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
929       raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
930     result = self._run_cmd_fn(["which", xen_cmd])
931     return not result.failed
932
933   def _CheckToolstackXlConfigured(self):
934     """Checks whether xl is enabled on an xl-capable node.
935
936     @rtype: bool
937     @returns: C{True} if 'xl' is enabled, C{False} otherwise
938
939     """
940     result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
941     if not result.failed:
942       return True
943     elif result.failed:
944       if "toolstack" in result.stderr:
945         return False
946       # xl fails for some other reason than the toolstack
947       else:
948         raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
949                                      % (constants.XEN_CMD_XL, result.stderr))
950
951
952 class XenPvmHypervisor(XenHypervisor):
953   """Xen PVM hypervisor interface"""
954
955   PARAMETERS = {
956     constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
957     constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
958     constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
959     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
960     constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
961     constants.HV_ROOT_PATH: hv_base.NO_CHECK,
962     constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
963     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
964     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
965     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
966     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
967     constants.HV_REBOOT_BEHAVIOR:
968       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
969     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
970     constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
971     constants.HV_CPU_WEIGHT:
972       (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
973     constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
974     constants.HV_XEN_CMD:
975       hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
976     constants.HV_XEN_CPUID: hv_base.NO_CHECK,
977     }
978
979   def _GetConfig(self, instance, startup_memory, block_devices):
980     """Write the Xen config file for the instance.
981
982     """
983     hvp = instance.hvparams
984     config = StringIO()
985     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
986
987     # if bootloader is True, use bootloader instead of kernel and ramdisk
988     # parameters.
989     if hvp[constants.HV_USE_BOOTLOADER]:
990       # bootloader handling
991       bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
992       if bootloader_path:
993         config.write("bootloader = '%s'\n" % bootloader_path)
994       else:
995         raise errors.HypervisorError("Bootloader enabled, but missing"
996                                      " bootloader path")
997
998       bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
999       if bootloader_args:
1000         config.write("bootargs = '%s'\n" % bootloader_args)
1001     else:
1002       # kernel handling
1003       kpath = hvp[constants.HV_KERNEL_PATH]
1004       config.write("kernel = '%s'\n" % kpath)
1005
1006       # initrd handling
1007       initrd_path = hvp[constants.HV_INITRD_PATH]
1008       if initrd_path:
1009         config.write("ramdisk = '%s'\n" % initrd_path)
1010
1011     # rest of the settings
1012     config.write("memory = %d\n" % startup_memory)
1013     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1014     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1015     cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1016     if cpu_pinning:
1017       config.write("%s\n" % cpu_pinning)
1018     cpu_cap = hvp[constants.HV_CPU_CAP]
1019     if cpu_cap:
1020       config.write("cpu_cap=%d\n" % cpu_cap)
1021     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1022     if cpu_weight:
1023       config.write("cpu_weight=%d\n" % cpu_weight)
1024
1025     config.write("name = '%s'\n" % instance.name)
1026
1027     vif_data = []
1028     for idx, nic in enumerate(instance.nics):
1029       nic_str = "mac=%s" % (nic.mac)
1030       ip = getattr(nic, "ip", None)
1031       if ip is not None:
1032         nic_str += ", ip=%s" % ip
1033       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1034         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1035       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
1036         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1037         if nic.nicparams[constants.NIC_VLAN]:
1038           nic_str += "%s" % nic.nicparams[constants.NIC_VLAN]
1039       if hvp[constants.HV_VIF_SCRIPT]:
1040         nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1041       vif_data.append("'%s'" % nic_str)
1042       self._WriteNICInfoFile(instance.name, idx, nic)
1043
1044     disk_data = \
1045       _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1046
1047     config.write("vif = [%s]\n" % ",".join(vif_data))
1048     config.write("disk = [%s]\n" % ",".join(disk_data))
1049
1050     if hvp[constants.HV_ROOT_PATH]:
1051       config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1052     config.write("on_poweroff = 'destroy'\n")
1053     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1054       config.write("on_reboot = 'restart'\n")
1055     else:
1056       config.write("on_reboot = 'destroy'\n")
1057     config.write("on_crash = 'restart'\n")
1058     config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1059
1060     cpuid = hvp[constants.HV_XEN_CPUID]
1061     if cpuid:
1062       config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1063
1064     return config.getvalue()
1065
1066
1067 class XenHvmHypervisor(XenHypervisor):
1068   """Xen HVM hypervisor interface"""
1069
1070   ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1071     pathutils.VNC_PASSWORD_FILE,
1072     ]
1073   ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1074     pathutils.VNC_PASSWORD_FILE,
1075     ]
1076
1077   PARAMETERS = {
1078     constants.HV_ACPI: hv_base.NO_CHECK,
1079     constants.HV_BOOT_ORDER: (True, ) +
1080       (lambda x: x and len(x.strip("acdn")) == 0,
1081        "Invalid boot order specified, must be one or more of [acdn]",
1082        None, None),
1083     constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1084     constants.HV_DISK_TYPE:
1085       hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1086     constants.HV_NIC_TYPE:
1087       hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1088     constants.HV_PAE: hv_base.NO_CHECK,
1089     constants.HV_VNC_BIND_ADDRESS:
1090       (False, netutils.IP4Address.IsValid,
1091        "VNC bind address is not a valid IP address", None, None),
1092     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1093     constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1094     constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1095     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1096     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1097     constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1098     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1099     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1100     # Add PCI passthrough
1101     constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1102     constants.HV_REBOOT_BEHAVIOR:
1103       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1104     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1105     constants.HV_CPU_CAP: hv_base.NO_CHECK,
1106     constants.HV_CPU_WEIGHT:
1107       (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1108     constants.HV_VIF_TYPE:
1109       hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1110     constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1111     constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1112     constants.HV_XEN_CMD:
1113       hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1114     constants.HV_XEN_CPUID: hv_base.NO_CHECK,
1115     }
1116
1117   def _GetConfig(self, instance, startup_memory, block_devices):
1118     """Create a Xen 3.1 HVM config file.
1119
1120     """
1121     hvp = instance.hvparams
1122
1123     config = StringIO()
1124
1125     # kernel handling
1126     kpath = hvp[constants.HV_KERNEL_PATH]
1127     config.write("kernel = '%s'\n" % kpath)
1128
1129     config.write("builder = 'hvm'\n")
1130     config.write("memory = %d\n" % startup_memory)
1131     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1132     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1133     cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1134     if cpu_pinning:
1135       config.write("%s\n" % cpu_pinning)
1136     cpu_cap = hvp[constants.HV_CPU_CAP]
1137     if cpu_cap:
1138       config.write("cpu_cap=%d\n" % cpu_cap)
1139     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1140     if cpu_weight:
1141       config.write("cpu_weight=%d\n" % cpu_weight)
1142
1143     config.write("name = '%s'\n" % instance.name)
1144     if hvp[constants.HV_PAE]:
1145       config.write("pae = 1\n")
1146     else:
1147       config.write("pae = 0\n")
1148     if hvp[constants.HV_ACPI]:
1149       config.write("acpi = 1\n")
1150     else:
1151       config.write("acpi = 0\n")
1152     if hvp[constants.HV_VIRIDIAN]:
1153       config.write("viridian = 1\n")
1154     else:
1155       config.write("viridian = 0\n")
1156
1157     config.write("apic = 1\n")
1158     config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1159     config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1160     config.write("sdl = 0\n")
1161     config.write("usb = 1\n")
1162     config.write("usbdevice = 'tablet'\n")
1163     config.write("vnc = 1\n")
1164     if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1165       config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1166     else:
1167       config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1168
1169     if instance.network_port > constants.VNC_BASE_PORT:
1170       display = instance.network_port - constants.VNC_BASE_PORT
1171       config.write("vncdisplay = %s\n" % display)
1172       config.write("vncunused = 0\n")
1173     else:
1174       config.write("# vncdisplay = 1\n")
1175       config.write("vncunused = 1\n")
1176
1177     vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1178     try:
1179       password = utils.ReadFile(vnc_pwd_file)
1180     except EnvironmentError, err:
1181       raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1182                                    (vnc_pwd_file, err))
1183
1184     config.write("vncpasswd = '%s'\n" % password.rstrip())
1185
1186     config.write("serial = 'pty'\n")
1187     if hvp[constants.HV_USE_LOCALTIME]:
1188       config.write("localtime = 1\n")
1189
1190     vif_data = []
1191     # Note: what is called 'nic_type' here, is used as value for the xen nic
1192     # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1193     # the 'vif_type' to avoid a clash of notation.
1194     nic_type = hvp[constants.HV_NIC_TYPE]
1195
1196     if nic_type is None:
1197       vif_type_str = ""
1198       if hvp[constants.HV_VIF_TYPE]:
1199         vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1200       # ensure old instances don't change
1201       nic_type_str = vif_type_str
1202     elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1203       nic_type_str = ", type=paravirtualized"
1204     else:
1205       # parameter 'model' is only valid with type 'ioemu'
1206       nic_type_str = ", model=%s, type=%s" % \
1207         (nic_type, constants.HT_HVM_VIF_IOEMU)
1208     for idx, nic in enumerate(instance.nics):
1209       nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1210       ip = getattr(nic, "ip", None)
1211       if ip is not None:
1212         nic_str += ", ip=%s" % ip
1213       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1214         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1215       if hvp[constants.HV_VIF_SCRIPT]:
1216         nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1217       vif_data.append("'%s'" % nic_str)
1218       self._WriteNICInfoFile(instance.name, idx, nic)
1219
1220     config.write("vif = [%s]\n" % ",".join(vif_data))
1221
1222     disk_data = \
1223       _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1224
1225     iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1226     if iso_path:
1227       iso = "'file:%s,hdc:cdrom,r'" % iso_path
1228       disk_data.append(iso)
1229
1230     config.write("disk = [%s]\n" % (",".join(disk_data)))
1231     # Add PCI passthrough
1232     pci_pass_arr = []
1233     pci_pass = hvp[constants.HV_PASSTHROUGH]
1234     if pci_pass:
1235       pci_pass_arr = pci_pass.split(";")
1236       config.write("pci = %s\n" % pci_pass_arr)
1237     config.write("on_poweroff = 'destroy'\n")
1238     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1239       config.write("on_reboot = 'restart'\n")
1240     else:
1241       config.write("on_reboot = 'destroy'\n")
1242     config.write("on_crash = 'restart'\n")
1243
1244     cpuid = hvp[constants.HV_XEN_CPUID]
1245     if cpuid:
1246       config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1247
1248     return config.getvalue()