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