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