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