Merge branch 'stable-2.8' into stable-2.9
[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 == constants.LD_FILE:
299       driver = _FILE_DRIVER_MAP[cfdev.physical_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 class XenHypervisor(hv_base.BaseHypervisor):
309   """Xen generic hypervisor interface
310
311   This is the Xen base class used for both Xen PVM and HVM. It contains
312   all the functionality that is identical for both.
313
314   """
315   CAN_MIGRATE = True
316   REBOOT_RETRY_COUNT = 60
317   REBOOT_RETRY_INTERVAL = 10
318   _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
319   _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
320   _DIRS = [_ROOT_DIR, _NICS_DIR]
321
322   ANCILLARY_FILES = [
323     XEND_CONFIG_FILE,
324     XL_CONFIG_FILE,
325     VIF_BRIDGE_SCRIPT,
326     ]
327   ANCILLARY_FILES_OPT = [
328     XL_CONFIG_FILE,
329     ]
330
331   def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
332     hv_base.BaseHypervisor.__init__(self)
333
334     if _cfgdir is None:
335       self._cfgdir = pathutils.XEN_CONFIG_DIR
336     else:
337       self._cfgdir = _cfgdir
338
339     if _run_cmd_fn is None:
340       self._run_cmd_fn = utils.RunCmd
341     else:
342       self._run_cmd_fn = _run_cmd_fn
343
344     self._cmd = _cmd
345
346   def _GetCommand(self, hvparams):
347     """Returns Xen command to use.
348
349     @type hvparams: dict of strings
350     @param hvparams: hypervisor parameters
351
352     """
353     if self._cmd is None:
354       if hvparams is None or constants.HV_XEN_CMD not in hvparams:
355         raise errors.HypervisorError("Cannot determine xen command.")
356       else:
357         cmd = hvparams[constants.HV_XEN_CMD]
358     else:
359       cmd = self._cmd
360
361     if cmd not in constants.KNOWN_XEN_COMMANDS:
362       raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
363
364     return cmd
365
366   def _RunXen(self, args, hvparams):
367     """Wrapper around L{utils.process.RunCmd} to run Xen command.
368
369     @type hvparams: dict of strings
370     @param hvparams: dictionary of hypervisor params
371     @see: L{utils.process.RunCmd}
372
373     """
374     cmd = [self._GetCommand(hvparams)]
375     cmd.extend(args)
376
377     return self._run_cmd_fn(cmd)
378
379   def _ConfigFileName(self, instance_name):
380     """Get the config file name for an instance.
381
382     @param instance_name: instance name
383     @type instance_name: str
384     @return: fully qualified path to instance config file
385     @rtype: str
386
387     """
388     return utils.PathJoin(self._cfgdir, instance_name)
389
390   @classmethod
391   def _WriteNICInfoFile(cls, instance_name, idx, nic):
392     """Write the Xen config file for the instance.
393
394     This version of the function just writes the config file from static data.
395
396     """
397     dirs = [(dname, constants.RUN_DIRS_MODE)
398             for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
399     utils.EnsureDirs(dirs)
400
401     cfg_file = cls._InstanceNICFile(instance_name, idx)
402     data = StringIO()
403
404     if nic.netinfo:
405       netinfo = objects.Network.FromDict(nic.netinfo)
406       data.write("NETWORK_NAME=%s\n" % netinfo.name)
407       if netinfo.network:
408         data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
409       if netinfo.gateway:
410         data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
411       if netinfo.network6:
412         data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
413       if netinfo.gateway6:
414         data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
415       if netinfo.mac_prefix:
416         data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
417       if netinfo.tags:
418         data.write("NETWORK_TAGS=%s\n" % "\ ".join(netinfo.tags))
419
420     data.write("MAC=%s\n" % nic.mac)
421     data.write("IP=%s\n" % nic.ip)
422     data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
423     data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
424
425     try:
426       utils.WriteFile(cfg_file, data=data.getvalue())
427     except EnvironmentError, err:
428       raise errors.HypervisorError("Cannot write Xen instance configuration"
429                                    " file %s: %s" % (cfg_file, err))
430
431   @classmethod
432   def _InstanceNICDir(cls, instance_name):
433     """Returns the directory holding the tap device files for a given instance.
434
435     """
436     return utils.PathJoin(cls._NICS_DIR, instance_name)
437
438   @classmethod
439   def _InstanceNICFile(cls, instance_name, seq):
440     """Returns the name of the file containing the tap device for a given NIC
441
442     """
443     return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
444
445   @classmethod
446   def _GetConfig(cls, instance, startup_memory, block_devices):
447     """Build Xen configuration for an instance.
448
449     """
450     raise NotImplementedError
451
452   def _WriteConfigFile(self, instance_name, data):
453     """Write the Xen config file for the instance.
454
455     This version of the function just writes the config file from static data.
456
457     """
458     # just in case it exists
459     utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
460
461     cfg_file = self._ConfigFileName(instance_name)
462     try:
463       utils.WriteFile(cfg_file, data=data)
464     except EnvironmentError, err:
465       raise errors.HypervisorError("Cannot write Xen instance configuration"
466                                    " file %s: %s" % (cfg_file, err))
467
468   def _ReadConfigFile(self, instance_name):
469     """Returns the contents of the instance config file.
470
471     """
472     filename = self._ConfigFileName(instance_name)
473
474     try:
475       file_content = utils.ReadFile(filename)
476     except EnvironmentError, err:
477       raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
478
479     return file_content
480
481   def _RemoveConfigFile(self, instance_name):
482     """Remove the xen configuration file.
483
484     """
485     utils.RemoveFile(self._ConfigFileName(instance_name))
486     try:
487       shutil.rmtree(self._InstanceNICDir(instance_name))
488     except OSError, err:
489       if err.errno != errno.ENOENT:
490         raise
491
492   def _StashConfigFile(self, instance_name):
493     """Move the Xen config file to the log directory and return its new path.
494
495     """
496     old_filename = self._ConfigFileName(instance_name)
497     base = ("%s-%s" %
498             (instance_name, utils.TimestampForFilename()))
499     new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
500     utils.RenameFile(old_filename, new_filename)
501     return new_filename
502
503   def _GetInstanceList(self, include_node, hvparams):
504     """Wrapper around module level L{_GetInstanceList}.
505
506     @type hvparams: dict of strings
507     @param hvparams: hypervisor parameters to be used on this node
508
509     """
510     return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
511                             include_node)
512
513   def ListInstances(self, hvparams=None):
514     """Get the list of running instances.
515
516     """
517     instance_list = self._GetInstanceList(False, hvparams)
518     names = [info[0] for info in instance_list]
519     return names
520
521   def GetInstanceInfo(self, instance_name, hvparams=None):
522     """Get instance properties.
523
524     @type instance_name: string
525     @param instance_name: the instance name
526     @type hvparams: dict of strings
527     @param hvparams: the instance's hypervisor params
528
529     @return: tuple (name, id, memory, vcpus, stat, times)
530
531     """
532     instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
533     result = None
534     for data in instance_list:
535       if data[0] == instance_name:
536         result = data
537         break
538     return result
539
540   def GetAllInstancesInfo(self, hvparams=None):
541     """Get properties of all instances.
542
543     @type hvparams: dict of strings
544     @param hvparams: hypervisor parameters
545     @return: list of tuples (name, id, memory, vcpus, stat, times)
546
547     """
548     return self._GetInstanceList(False, hvparams)
549
550   def _MakeConfigFile(self, instance, startup_memory, block_devices):
551     """Gather configuration details and write to disk.
552
553     See L{_GetConfig} for arguments.
554
555     """
556     buf = StringIO()
557     buf.write("# Automatically generated by Ganeti. Do not edit!\n")
558     buf.write("\n")
559     buf.write(self._GetConfig(instance, startup_memory, block_devices))
560     buf.write("\n")
561
562     self._WriteConfigFile(instance.name, buf.getvalue())
563
564   def StartInstance(self, instance, block_devices, startup_paused):
565     """Start an instance.
566
567     """
568     startup_memory = self._InstanceStartupMemory(instance,
569                                                  hvparams=instance.hvparams)
570
571     self._MakeConfigFile(instance, startup_memory, block_devices)
572
573     cmd = ["create"]
574     if startup_paused:
575       cmd.append("-p")
576     cmd.append(self._ConfigFileName(instance.name))
577
578     result = self._RunXen(cmd, instance.hvparams)
579     if result.failed:
580       # Move the Xen configuration file to the log directory to avoid
581       # leaving a stale config file behind.
582       stashed_config = self._StashConfigFile(instance.name)
583       raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
584                                    " config file to %s" %
585                                    (instance.name, result.fail_reason,
586                                     result.output, stashed_config))
587
588   def StopInstance(self, instance, force=False, retry=False, name=None):
589     """Stop an instance.
590
591     """
592     if name is None:
593       name = instance.name
594
595     return self._StopInstance(name, force, instance.hvparams)
596
597   def _StopInstance(self, name, force, hvparams):
598     """Stop an instance.
599
600     @type name: string
601     @param name: name of the instance to be shutdown
602     @type force: boolean
603     @param force: flag specifying whether shutdown should be forced
604     @type hvparams: dict of string
605     @param hvparams: hypervisor parameters of the instance
606
607     """
608     if force:
609       action = "destroy"
610     else:
611       action = "shutdown"
612
613     result = self._RunXen([action, name], hvparams)
614     if result.failed:
615       raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
616                                    (name, result.fail_reason, result.output))
617
618     # Remove configuration file if stopping/starting instance was successful
619     self._RemoveConfigFile(name)
620
621   def RebootInstance(self, instance):
622     """Reboot an instance.
623
624     """
625     ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
626
627     if ini_info is None:
628       raise errors.HypervisorError("Failed to reboot instance %s,"
629                                    " not running" % instance.name)
630
631     result = self._RunXen(["reboot", instance.name], instance.hvparams)
632     if result.failed:
633       raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
634                                    (instance.name, result.fail_reason,
635                                     result.output))
636
637     def _CheckInstance():
638       new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
639
640       # check if the domain ID has changed or the run time has decreased
641       if (new_info is not None and
642           (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
643         return
644
645       raise utils.RetryAgain()
646
647     try:
648       utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
649                   self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
650     except utils.RetryTimeout:
651       raise errors.HypervisorError("Failed to reboot instance %s: instance"
652                                    " did not reboot in the expected interval" %
653                                    (instance.name, ))
654
655   def BalloonInstanceMemory(self, instance, mem):
656     """Balloon an instance memory to a certain value.
657
658     @type instance: L{objects.Instance}
659     @param instance: instance to be accepted
660     @type mem: int
661     @param mem: actual memory size to use for instance runtime
662
663     """
664     result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
665     if result.failed:
666       raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
667                                    (instance.name, result.fail_reason,
668                                     result.output))
669
670     # Update configuration file
671     cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
672     cmd.append(self._ConfigFileName(instance.name))
673
674     result = utils.RunCmd(cmd)
675     if result.failed:
676       raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
677                                    (instance.name, result.fail_reason,
678                                     result.output))
679
680   def GetNodeInfo(self, hvparams=None):
681     """Return information about the node.
682
683     @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
684
685     """
686     result = self._RunXen(["info"], hvparams)
687     if result.failed:
688       logging.error("Can't retrieve xen hypervisor information (%s): %s",
689                     result.fail_reason, result.output)
690       return None
691
692     instance_list = self._GetInstanceList(True, hvparams)
693     return _GetNodeInfo(result.stdout, instance_list)
694
695   def GetInstanceConsole(self, instance, primary_node, hvparams, beparams):
696     """Return a command for connecting to the console of an instance.
697
698     """
699     xen_cmd = self._GetCommand(hvparams)
700     return objects.InstanceConsole(instance=instance.name,
701                                    kind=constants.CONS_SSH,
702                                    host=primary_node.name,
703                                    user=constants.SSH_CONSOLE_USER,
704                                    command=[pathutils.XEN_CONSOLE_WRAPPER,
705                                             xen_cmd, instance.name])
706
707   def Verify(self, hvparams=None):
708     """Verify the hypervisor.
709
710     For Xen, this verifies that the xend process is running.
711
712     @type hvparams: dict of strings
713     @param hvparams: hypervisor parameters to be verified against
714
715     @return: Problem description if something is wrong, C{None} otherwise
716
717     """
718     if hvparams is None:
719       return "Could not verify the hypervisor, because no hvparams were" \
720              " provided."
721
722     if constants.HV_XEN_CMD in hvparams:
723       xen_cmd = hvparams[constants.HV_XEN_CMD]
724       try:
725         self._CheckToolstack(xen_cmd)
726       except errors.HypervisorError:
727         return "The configured xen toolstack '%s' is not available on this" \
728                " node." % xen_cmd
729
730     result = self._RunXen(["info"], hvparams)
731     if result.failed:
732       return "Retrieving information from xen failed: %s, %s" % \
733         (result.fail_reason, result.output)
734
735     return None
736
737   def MigrationInfo(self, instance):
738     """Get instance information to perform a migration.
739
740     @type instance: L{objects.Instance}
741     @param instance: instance to be migrated
742     @rtype: string
743     @return: content of the xen config file
744
745     """
746     return self._ReadConfigFile(instance.name)
747
748   def AcceptInstance(self, instance, info, target):
749     """Prepare to accept an instance.
750
751     @type instance: L{objects.Instance}
752     @param instance: instance to be accepted
753     @type info: string
754     @param info: content of the xen config file on the source node
755     @type target: string
756     @param target: target host (usually ip), on this node
757
758     """
759     pass
760
761   def FinalizeMigrationDst(self, instance, info, success):
762     """Finalize an instance migration.
763
764     After a successful migration we write the xen config file.
765     We do nothing on a failure, as we did not change anything at accept time.
766
767     @type instance: L{objects.Instance}
768     @param instance: instance whose migration is being finalized
769     @type info: string
770     @param info: content of the xen config file on the source node
771     @type success: boolean
772     @param success: whether the migration was a success or a failure
773
774     """
775     if success:
776       self._WriteConfigFile(instance.name, info)
777
778   def MigrateInstance(self, cluster_name, instance, target, live):
779     """Migrate an instance to a target node.
780
781     The migration will not be attempted if the instance is not
782     currently running.
783
784     @type instance: L{objects.Instance}
785     @param instance: the instance to be migrated
786     @type target: string
787     @param target: ip address of the target node
788     @type live: boolean
789     @param live: perform a live migration
790
791     """
792     port = instance.hvparams[constants.HV_MIGRATION_PORT]
793
794     return self._MigrateInstance(cluster_name, instance.name, target, port,
795                                  live, instance.hvparams)
796
797   def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
798                        hvparams, _ping_fn=netutils.TcpPing):
799     """Migrate an instance to a target node.
800
801     @see: L{MigrateInstance} for details
802
803     """
804     if hvparams is None:
805       raise errors.HypervisorError("No hvparams provided.")
806
807     if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
808       raise errors.HypervisorError("Instance not running, cannot migrate")
809
810     cmd = self._GetCommand(hvparams)
811
812     if (cmd == constants.XEN_CMD_XM and
813         not _ping_fn(target, port, live_port_needed=True)):
814       raise errors.HypervisorError("Remote host %s not listening on port"
815                                    " %s, cannot migrate" % (target, port))
816
817     args = ["migrate"]
818
819     if cmd == constants.XEN_CMD_XM:
820       args.extend(["-p", "%d" % port])
821       if live:
822         args.append("-l")
823
824     elif cmd == constants.XEN_CMD_XL:
825       args.extend([
826         "-s", constants.XL_SSH_CMD % cluster_name,
827         "-C", self._ConfigFileName(instance_name),
828         ])
829
830     else:
831       raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
832
833     args.extend([instance_name, target])
834
835     result = self._RunXen(args, hvparams)
836     if result.failed:
837       raise errors.HypervisorError("Failed to migrate instance %s: %s" %
838                                    (instance_name, result.output))
839
840   def FinalizeMigrationSource(self, instance, success, live):
841     """Finalize the instance migration on the source node.
842
843     @type instance: L{objects.Instance}
844     @param instance: the instance that was migrated
845     @type success: bool
846     @param success: whether the migration succeeded or not
847     @type live: bool
848     @param live: whether the user requested a live migration or not
849
850     """
851     # pylint: disable=W0613
852     if success:
853       # remove old xen file after migration succeeded
854       try:
855         self._RemoveConfigFile(instance.name)
856       except EnvironmentError:
857         logging.exception("Failure while removing instance config file")
858
859   def GetMigrationStatus(self, instance):
860     """Get the migration status
861
862     As MigrateInstance for Xen is still blocking, if this method is called it
863     means that MigrateInstance has completed successfully. So we can safely
864     assume that the migration was successful and notify this fact to the client.
865
866     @type instance: L{objects.Instance}
867     @param instance: the instance that is being migrated
868     @rtype: L{objects.MigrationStatus}
869     @return: the status of the current migration (one of
870              L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
871              progress info that can be retrieved from the hypervisor
872
873     """
874     return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
875
876   def PowercycleNode(self, hvparams=None):
877     """Xen-specific powercycle.
878
879     This first does a Linux reboot (which triggers automatically a Xen
880     reboot), and if that fails it tries to do a Xen reboot. The reason
881     we don't try a Xen reboot first is that the xen reboot launches an
882     external command which connects to the Xen hypervisor, and that
883     won't work in case the root filesystem is broken and/or the xend
884     daemon is not working.
885
886     @type hvparams: dict of strings
887     @param hvparams: hypervisor params to be used on this node
888
889     """
890     try:
891       self.LinuxPowercycle()
892     finally:
893       xen_cmd = self._GetCommand(hvparams)
894       utils.RunCmd([xen_cmd, "debug", "R"])
895
896   def _CheckToolstack(self, xen_cmd):
897     """Check whether the given toolstack is available on the node.
898
899     @type xen_cmd: string
900     @param xen_cmd: xen command (e.g. 'xm' or 'xl')
901
902     """
903     binary_found = self._CheckToolstackBinary(xen_cmd)
904     if not binary_found:
905       raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
906     elif xen_cmd == constants.XEN_CMD_XL:
907       if not self._CheckToolstackXlConfigured():
908         raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
909                                      "node." % xen_cmd)
910
911   def _CheckToolstackBinary(self, xen_cmd):
912     """Checks whether the xen command's binary is found on the machine.
913
914     """
915     if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
916       raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
917     result = self._run_cmd_fn(["which", xen_cmd])
918     return not result.failed
919
920   def _CheckToolstackXlConfigured(self):
921     """Checks whether xl is enabled on an xl-capable node.
922
923     @rtype: bool
924     @returns: C{True} if 'xl' is enabled, C{False} otherwise
925
926     """
927     result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
928     if not result.failed:
929       return True
930     elif result.failed:
931       if "toolstack" in result.stderr:
932         return False
933       # xl fails for some other reason than the toolstack
934       else:
935         raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
936                                      % (constants.XEN_CMD_XL, result.stderr))
937
938
939 class XenPvmHypervisor(XenHypervisor):
940   """Xen PVM hypervisor interface"""
941
942   PARAMETERS = {
943     constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
944     constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
945     constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
946     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
947     constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
948     constants.HV_ROOT_PATH: hv_base.NO_CHECK,
949     constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
950     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
951     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
952     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
953     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
954     constants.HV_REBOOT_BEHAVIOR:
955       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
956     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
957     constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
958     constants.HV_CPU_WEIGHT:
959       (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
960     constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
961     constants.HV_XEN_CMD:
962       hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
963     }
964
965   def _GetConfig(self, instance, startup_memory, block_devices):
966     """Write the Xen config file for the instance.
967
968     """
969     hvp = instance.hvparams
970     config = StringIO()
971     config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
972
973     # if bootloader is True, use bootloader instead of kernel and ramdisk
974     # parameters.
975     if hvp[constants.HV_USE_BOOTLOADER]:
976       # bootloader handling
977       bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
978       if bootloader_path:
979         config.write("bootloader = '%s'\n" % bootloader_path)
980       else:
981         raise errors.HypervisorError("Bootloader enabled, but missing"
982                                      " bootloader path")
983
984       bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
985       if bootloader_args:
986         config.write("bootargs = '%s'\n" % bootloader_args)
987     else:
988       # kernel handling
989       kpath = hvp[constants.HV_KERNEL_PATH]
990       config.write("kernel = '%s'\n" % kpath)
991
992       # initrd handling
993       initrd_path = hvp[constants.HV_INITRD_PATH]
994       if initrd_path:
995         config.write("ramdisk = '%s'\n" % initrd_path)
996
997     # rest of the settings
998     config.write("memory = %d\n" % startup_memory)
999     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1000     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1001     cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1002     if cpu_pinning:
1003       config.write("%s\n" % cpu_pinning)
1004     cpu_cap = hvp[constants.HV_CPU_CAP]
1005     if cpu_cap:
1006       config.write("cpu_cap=%d\n" % cpu_cap)
1007     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1008     if cpu_weight:
1009       config.write("cpu_weight=%d\n" % cpu_weight)
1010
1011     config.write("name = '%s'\n" % instance.name)
1012
1013     vif_data = []
1014     for idx, nic in enumerate(instance.nics):
1015       nic_str = "mac=%s" % (nic.mac)
1016       ip = getattr(nic, "ip", None)
1017       if ip is not None:
1018         nic_str += ", ip=%s" % ip
1019       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1020         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1021       if hvp[constants.HV_VIF_SCRIPT]:
1022         nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1023       vif_data.append("'%s'" % nic_str)
1024       self._WriteNICInfoFile(instance.name, idx, nic)
1025
1026     disk_data = \
1027       _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1028
1029     config.write("vif = [%s]\n" % ",".join(vif_data))
1030     config.write("disk = [%s]\n" % ",".join(disk_data))
1031
1032     if hvp[constants.HV_ROOT_PATH]:
1033       config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1034     config.write("on_poweroff = 'destroy'\n")
1035     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1036       config.write("on_reboot = 'restart'\n")
1037     else:
1038       config.write("on_reboot = 'destroy'\n")
1039     config.write("on_crash = 'restart'\n")
1040     config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1041
1042     return config.getvalue()
1043
1044
1045 class XenHvmHypervisor(XenHypervisor):
1046   """Xen HVM hypervisor interface"""
1047
1048   ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1049     pathutils.VNC_PASSWORD_FILE,
1050     ]
1051   ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1052     pathutils.VNC_PASSWORD_FILE,
1053     ]
1054
1055   PARAMETERS = {
1056     constants.HV_ACPI: hv_base.NO_CHECK,
1057     constants.HV_BOOT_ORDER: (True, ) +
1058       (lambda x: x and len(x.strip("acdn")) == 0,
1059        "Invalid boot order specified, must be one or more of [acdn]",
1060        None, None),
1061     constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1062     constants.HV_DISK_TYPE:
1063       hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1064     constants.HV_NIC_TYPE:
1065       hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1066     constants.HV_PAE: hv_base.NO_CHECK,
1067     constants.HV_VNC_BIND_ADDRESS:
1068       (False, netutils.IP4Address.IsValid,
1069        "VNC bind address is not a valid IP address", None, None),
1070     constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1071     constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1072     constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1073     constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1074     constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1075     constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1076     # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1077     constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1078     # Add PCI passthrough
1079     constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1080     constants.HV_REBOOT_BEHAVIOR:
1081       hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1082     constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1083     constants.HV_CPU_CAP: hv_base.NO_CHECK,
1084     constants.HV_CPU_WEIGHT:
1085       (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1086     constants.HV_VIF_TYPE:
1087       hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1088     constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1089     constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1090     constants.HV_XEN_CMD:
1091       hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1092     }
1093
1094   def _GetConfig(self, instance, startup_memory, block_devices):
1095     """Create a Xen 3.1 HVM config file.
1096
1097     """
1098     hvp = instance.hvparams
1099
1100     config = StringIO()
1101
1102     # kernel handling
1103     kpath = hvp[constants.HV_KERNEL_PATH]
1104     config.write("kernel = '%s'\n" % kpath)
1105
1106     config.write("builder = 'hvm'\n")
1107     config.write("memory = %d\n" % startup_memory)
1108     config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1109     config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1110     cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1111     if cpu_pinning:
1112       config.write("%s\n" % cpu_pinning)
1113     cpu_cap = hvp[constants.HV_CPU_CAP]
1114     if cpu_cap:
1115       config.write("cpu_cap=%d\n" % cpu_cap)
1116     cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1117     if cpu_weight:
1118       config.write("cpu_weight=%d\n" % cpu_weight)
1119
1120     config.write("name = '%s'\n" % instance.name)
1121     if hvp[constants.HV_PAE]:
1122       config.write("pae = 1\n")
1123     else:
1124       config.write("pae = 0\n")
1125     if hvp[constants.HV_ACPI]:
1126       config.write("acpi = 1\n")
1127     else:
1128       config.write("acpi = 0\n")
1129     if hvp[constants.HV_VIRIDIAN]:
1130       config.write("viridian = 1\n")
1131     else:
1132       config.write("viridian = 0\n")
1133
1134     config.write("apic = 1\n")
1135     config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1136     config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1137     config.write("sdl = 0\n")
1138     config.write("usb = 1\n")
1139     config.write("usbdevice = 'tablet'\n")
1140     config.write("vnc = 1\n")
1141     if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1142       config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1143     else:
1144       config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1145
1146     if instance.network_port > constants.VNC_BASE_PORT:
1147       display = instance.network_port - constants.VNC_BASE_PORT
1148       config.write("vncdisplay = %s\n" % display)
1149       config.write("vncunused = 0\n")
1150     else:
1151       config.write("# vncdisplay = 1\n")
1152       config.write("vncunused = 1\n")
1153
1154     vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1155     try:
1156       password = utils.ReadFile(vnc_pwd_file)
1157     except EnvironmentError, err:
1158       raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1159                                    (vnc_pwd_file, err))
1160
1161     config.write("vncpasswd = '%s'\n" % password.rstrip())
1162
1163     config.write("serial = 'pty'\n")
1164     if hvp[constants.HV_USE_LOCALTIME]:
1165       config.write("localtime = 1\n")
1166
1167     vif_data = []
1168     # Note: what is called 'nic_type' here, is used as value for the xen nic
1169     # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1170     # the 'vif_type' to avoid a clash of notation.
1171     nic_type = hvp[constants.HV_NIC_TYPE]
1172
1173     if nic_type is None:
1174       vif_type_str = ""
1175       if hvp[constants.HV_VIF_TYPE]:
1176         vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1177       # ensure old instances don't change
1178       nic_type_str = vif_type_str
1179     elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1180       nic_type_str = ", type=paravirtualized"
1181     else:
1182       # parameter 'model' is only valid with type 'ioemu'
1183       nic_type_str = ", model=%s, type=%s" % \
1184         (nic_type, constants.HT_HVM_VIF_IOEMU)
1185     for idx, nic in enumerate(instance.nics):
1186       nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1187       ip = getattr(nic, "ip", None)
1188       if ip is not None:
1189         nic_str += ", ip=%s" % ip
1190       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1191         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1192       if hvp[constants.HV_VIF_SCRIPT]:
1193         nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1194       vif_data.append("'%s'" % nic_str)
1195       self._WriteNICInfoFile(instance.name, idx, nic)
1196
1197     config.write("vif = [%s]\n" % ",".join(vif_data))
1198
1199     disk_data = \
1200       _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1201
1202     iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1203     if iso_path:
1204       iso = "'file:%s,hdc:cdrom,r'" % iso_path
1205       disk_data.append(iso)
1206
1207     config.write("disk = [%s]\n" % (",".join(disk_data)))
1208     # Add PCI passthrough
1209     pci_pass_arr = []
1210     pci_pass = hvp[constants.HV_PASSTHROUGH]
1211     if pci_pass:
1212       pci_pass_arr = pci_pass.split(";")
1213       config.write("pci = %s\n" % pci_pass_arr)
1214     config.write("on_poweroff = 'destroy'\n")
1215     if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1216       config.write("on_reboot = 'restart'\n")
1217     else:
1218       config.write("on_reboot = 'destroy'\n")
1219     config.write("on_crash = 'restart'\n")
1220
1221     return config.getvalue()