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