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