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