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