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