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