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