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