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