1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
38 """Xen generic hypervisor interface
39
40 This is the Xen base class used for both Xen PVM and HVM. It contains
41 all the functionality that is identical for both.
42
43 """
44 CAN_MIGRATE = True
45 REBOOT_RETRY_COUNT = 60
46 REBOOT_RETRY_INTERVAL = 10
47
48 ANCILLARY_FILES = [
49 "/etc/xen/xend-config.sxp",
50 "/etc/xen/scripts/vif-bridge",
51 ]
52
53 @classmethod
55 """Write the Xen config file for the instance.
56
57 """
58 raise NotImplementedError
59
60 @staticmethod
62 """Write the Xen config file for the instance.
63
64 This version of the function just writes the config file from static data.
65
66 """
67 utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
68
69 @staticmethod
71 """Returns the contents of the instance config file.
72
73 """
74 try:
75 file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
76 except EnvironmentError, err:
77 raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
78 return file_content
79
80 @staticmethod
82 """Remove the xen configuration file.
83
84 """
85 utils.RemoveFile("/etc/xen/%s" % instance_name)
86
87 @staticmethod
89 """Helper function for L{_GetXMList} to run "xm list".
90
91 """
92 result = utils.RunCmd(["xm", "list"])
93 if result.failed:
94 logging.error("xm list failed (%s): %s", result.fail_reason,
95 result.output)
96 xmlist_errors.append(result)
97 raise utils.RetryAgain()
98
99
100 return result.stdout.splitlines()[1:]
101
102 @classmethod
104 """Return the list of running instances.
105
106 If the include_node argument is True, then we return information
107 for dom0 also, otherwise we filter that from the return value.
108
109 @return: list of (name, id, memory, vcpus, state, time spent)
110
111 """
112 xmlist_errors = []
113 try:
114 lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
115 except utils.RetryTimeout:
116 if xmlist_errors:
117 xmlist_result = xmlist_errors.pop()
118
119 errmsg = ("xm list failed, timeout exceeded (%s): %s" %
120 (xmlist_result.fail_reason, xmlist_result.output))
121 else:
122 errmsg = "xm list failed"
123
124 raise errors.HypervisorError(errmsg)
125
126 result = []
127 for line in lines:
128
129
130
131 data = line.split()
132 if len(data) != 6:
133 raise errors.HypervisorError("Can't parse output of xm list,"
134 " line: %s" % line)
135 try:
136 data[1] = int(data[1])
137 data[2] = int(data[2])
138 data[3] = int(data[3])
139 data[5] = float(data[5])
140 except (TypeError, ValueError), err:
141 raise errors.HypervisorError("Can't parse output of xm list,"
142 " line: %s, error: %s" % (line, err))
143
144
145 if include_node or data[0] != "Domain-0":
146 result.append(data)
147
148 return result
149
151 """Get the list of running instances.
152
153 """
154 xm_list = self._GetXMList(False)
155 names = [info[0] for info in xm_list]
156 return names
157
159 """Get instance properties.
160
161 @param instance_name: the instance name
162
163 @return: tuple (name, id, memory, vcpus, stat, times)
164
165 """
166 xm_list = self._GetXMList(instance_name == "Domain-0")
167 result = None
168 for data in xm_list:
169 if data[0] == instance_name:
170 result = data
171 break
172 return result
173
175 """Get properties of all instances.
176
177 @return: list of tuples (name, id, memory, vcpus, stat, times)
178
179 """
180 xm_list = self._GetXMList(False)
181 return xm_list
182
183 - def StartInstance(self, instance, block_devices, startup_paused):
184 """Start an instance.
185
186 """
187 self._WriteConfigFile(instance, block_devices)
188 cmd = ["xm", "create"]
189 if startup_paused:
190 cmd.extend(["--paused"])
191 cmd.extend([instance.name])
192 result = utils.RunCmd(cmd)
193
194 if result.failed:
195 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
196 (instance.name, result.fail_reason,
197 result.output))
198
199 - def StopInstance(self, instance, force=False, retry=False, name=None):
200 """Stop an instance.
201
202 """
203 if name is None:
204 name = instance.name
205 self._RemoveConfigFile(name)
206 if force:
207 command = ["xm", "destroy", name]
208 else:
209 command = ["xm", "shutdown", name]
210 result = utils.RunCmd(command)
211
212 if result.failed:
213 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
214 (name, result.fail_reason, result.output))
215
241
242 try:
243 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
244 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
245 except utils.RetryTimeout:
246 raise errors.HypervisorError("Failed to reboot instance %s: instance"
247 " did not reboot in the expected interval" %
248 (instance.name, ))
249
251 """Return information about the node.
252
253 @return: a dict with the following keys (memory values in MiB):
254 - memory_total: the total memory size on the node
255 - memory_free: the available memory on the node for instances
256 - memory_dom0: the memory used by the node itself, if available
257 - nr_cpus: total number of CPUs
258 - nr_nodes: in a NUMA system, the number of domains
259 - nr_sockets: the number of physical CPU sockets in the node
260
261 """
262
263 result = utils.RunCmd(["xm", "info"])
264 if result.failed:
265 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
266 result.output)
267 return None
268
269 xmoutput = result.stdout.splitlines()
270 result = {}
271 cores_per_socket = threads_per_core = nr_cpus = None
272 for line in xmoutput:
273 splitfields = line.split(":", 1)
274
275 if len(splitfields) > 1:
276 key = splitfields[0].strip()
277 val = splitfields[1].strip()
278 if key == "memory" or key == "total_memory":
279 result["memory_total"] = int(val)
280 elif key == "free_memory":
281 result["memory_free"] = int(val)
282 elif key == "nr_cpus":
283 nr_cpus = result["cpu_total"] = int(val)
284 elif key == "nr_nodes":
285 result["cpu_nodes"] = int(val)
286 elif key == "cores_per_socket":
287 cores_per_socket = int(val)
288 elif key == "threads_per_core":
289 threads_per_core = int(val)
290
291 if (cores_per_socket is not None and
292 threads_per_core is not None and nr_cpus is not None):
293 result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
294
295 dom0_info = self.GetInstanceInfo("Domain-0")
296 if dom0_info is not None:
297 result["memory_dom0"] = dom0_info[2]
298
299 return result
300
301 @classmethod
312
314 """Verify the hypervisor.
315
316 For Xen, this verifies that the xend process is running.
317
318 """
319 result = utils.RunCmd(["xm", "info"])
320 if result.failed:
321 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
322
323 @staticmethod
325 """Get disk directive for xen config file.
326
327 This method builds the xen config disk directive according to the
328 given disk_template and block_devices.
329
330 @param block_devices: list of tuples (cfdev, rldev):
331 - cfdev: dict containing ganeti config disk part
332 - rldev: ganeti.bdev.BlockDev object
333 @param blockdev_prefix: a string containing blockdevice prefix,
334 e.g. "sd" for /dev/sda
335
336 @return: string containing disk directive for xen instance config file
337
338 """
339 FILE_DRIVER_MAP = {
340 constants.FD_LOOP: "file",
341 constants.FD_BLKTAP: "tap:aio",
342 }
343 disk_data = []
344 if len(block_devices) > 24:
345
346 raise errors.HypervisorError("Too many disks")
347 namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
348 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
349 if cfdev.mode == constants.DISK_RDWR:
350 mode = "w"
351 else:
352 mode = "r"
353 if cfdev.dev_type == constants.LD_FILE:
354 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
355 dev_path, sd_name, mode)
356 else:
357 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
358 disk_data.append(line)
359
360 return disk_data
361
363 """Get instance information to perform a migration.
364
365 @type instance: L{objects.Instance}
366 @param instance: instance to be migrated
367 @rtype: string
368 @return: content of the xen config file
369
370 """
371 return self._ReadConfigFile(instance.name)
372
374 """Prepare to accept an instance.
375
376 @type instance: L{objects.Instance}
377 @param instance: instance to be accepted
378 @type info: string
379 @param info: content of the xen config file on the source node
380 @type target: string
381 @param target: target host (usually ip), on this node
382
383 """
384 pass
385
387 """Finalize an instance migration.
388
389 After a successful migration we write the xen config file.
390 We do nothing on a failure, as we did not change anything at accept time.
391
392 @type instance: L{objects.Instance}
393 @param instance: instance whose migration is being finalized
394 @type info: string
395 @param info: content of the xen config file on the source node
396 @type success: boolean
397 @param success: whether the migration was a success or a failure
398
399 """
400 if success:
401 self._WriteConfigFileStatic(instance.name, info)
402
404 """Migrate an instance to a target node.
405
406 The migration will not be attempted if the instance is not
407 currently running.
408
409 @type instance: L{objects.Instance}
410 @param instance: the instance to be migrated
411 @type target: string
412 @param target: ip address of the target node
413 @type live: boolean
414 @param live: perform a live migration
415
416 """
417 if self.GetInstanceInfo(instance.name) is None:
418 raise errors.HypervisorError("Instance not running, cannot migrate")
419
420 port = instance.hvparams[constants.HV_MIGRATION_PORT]
421
422 if not netutils.TcpPing(target, port, live_port_needed=True):
423 raise errors.HypervisorError("Remote host %s not listening on port"
424 " %s, cannot migrate" % (target, port))
425
426 args = ["xm", "migrate", "-p", "%d" % port]
427 if live:
428 args.append("-l")
429 args.extend([instance.name, target])
430 result = utils.RunCmd(args)
431 if result.failed:
432 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
433 (instance.name, result.output))
434
435 try:
436 self._RemoveConfigFile(instance.name)
437 except EnvironmentError:
438 logging.exception("Failure while removing instance config file")
439
440 @classmethod
442 """Xen-specific powercycle.
443
444 This first does a Linux reboot (which triggers automatically a Xen
445 reboot), and if that fails it tries to do a Xen reboot. The reason
446 we don't try a Xen reboot first is that the xen reboot launches an
447 external command which connects to the Xen hypervisor, and that
448 won't work in case the root filesystem is broken and/or the xend
449 daemon is not working.
450
451 """
452 try:
453 cls.LinuxPowercycle()
454 finally:
455 utils.RunCmd(["xm", "debug", "R"])
456
459 """Xen PVM hypervisor interface"""
460
461 PARAMETERS = {
462 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
463 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
464 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
465 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
466 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
467 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
468 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
469 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
470 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
471
472 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
473 constants.HV_REBOOT_BEHAVIOR:
474 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
475 }
476
477 @classmethod
479 """Write the Xen config file for the instance.
480
481 """
482 hvp = instance.hvparams
483 config = StringIO()
484 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
485
486
487
488 if hvp[constants.HV_USE_BOOTLOADER]:
489
490 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
491 if bootloader_path:
492 config.write("bootloader = '%s'\n" % bootloader_path)
493 else:
494 raise errors.HypervisorError("Bootloader enabled, but missing"
495 " bootloader path")
496
497 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
498 if bootloader_args:
499 config.write("bootargs = '%s'\n" % bootloader_args)
500 else:
501
502 kpath = hvp[constants.HV_KERNEL_PATH]
503 config.write("kernel = '%s'\n" % kpath)
504
505
506 initrd_path = hvp[constants.HV_INITRD_PATH]
507 if initrd_path:
508 config.write("ramdisk = '%s'\n" % initrd_path)
509
510
511 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
512 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
513 config.write("name = '%s'\n" % instance.name)
514
515 vif_data = []
516 for nic in instance.nics:
517 nic_str = "mac=%s" % (nic.mac)
518 ip = getattr(nic, "ip", None)
519 if ip is not None:
520 nic_str += ", ip=%s" % ip
521 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
522 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
523 vif_data.append("'%s'" % nic_str)
524
525 disk_data = cls._GetConfigFileDiskData(block_devices,
526 hvp[constants.HV_BLOCKDEV_PREFIX])
527
528 config.write("vif = [%s]\n" % ",".join(vif_data))
529 config.write("disk = [%s]\n" % ",".join(disk_data))
530
531 if hvp[constants.HV_ROOT_PATH]:
532 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
533 config.write("on_poweroff = 'destroy'\n")
534 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
535 config.write("on_reboot = 'restart'\n")
536 else:
537 config.write("on_reboot = 'destroy'\n")
538 config.write("on_crash = 'restart'\n")
539 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
540
541 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
542 try:
543 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
544 except EnvironmentError, err:
545 raise errors.HypervisorError("Cannot write Xen instance confile"
546 " file /etc/xen/%s: %s" %
547 (instance.name, err))
548
549 return True
550
553 """Xen HVM hypervisor interface"""
554
555 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
556 constants.VNC_PASSWORD_FILE,
557 ]
558
559 PARAMETERS = {
560 constants.HV_ACPI: hv_base.NO_CHECK,
561 constants.HV_BOOT_ORDER: (True, ) +
562 (lambda x: x and len(x.strip("acdn")) == 0,
563 "Invalid boot order specified, must be one or more of [acdn]",
564 None, None),
565 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
566 constants.HV_DISK_TYPE:
567 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
568 constants.HV_NIC_TYPE:
569 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
570 constants.HV_PAE: hv_base.NO_CHECK,
571 constants.HV_VNC_BIND_ADDRESS:
572 (False, netutils.IP4Address.IsValid,
573 "VNC bind address is not a valid IP address", None, None),
574 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
575 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
576 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
577 constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
578 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
579 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
580
581 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
582 constants.HV_REBOOT_BEHAVIOR:
583 hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
584 }
585
586 @classmethod
588 """Create a Xen 3.1 HVM config file.
589
590 """
591 hvp = instance.hvparams
592
593 config = StringIO()
594 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
595
596
597 kpath = hvp[constants.HV_KERNEL_PATH]
598 config.write("kernel = '%s'\n" % kpath)
599
600 config.write("builder = 'hvm'\n")
601 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
602 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
603 config.write("name = '%s'\n" % instance.name)
604 if hvp[constants.HV_PAE]:
605 config.write("pae = 1\n")
606 else:
607 config.write("pae = 0\n")
608 if hvp[constants.HV_ACPI]:
609 config.write("acpi = 1\n")
610 else:
611 config.write("acpi = 0\n")
612 config.write("apic = 1\n")
613 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
614 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
615 config.write("sdl = 0\n")
616 config.write("usb = 1\n")
617 config.write("usbdevice = 'tablet'\n")
618 config.write("vnc = 1\n")
619 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
620 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
621 else:
622 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
623
624 if instance.network_port > constants.VNC_BASE_PORT:
625 display = instance.network_port - constants.VNC_BASE_PORT
626 config.write("vncdisplay = %s\n" % display)
627 config.write("vncunused = 0\n")
628 else:
629 config.write("# vncdisplay = 1\n")
630 config.write("vncunused = 1\n")
631
632 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
633 try:
634 password = utils.ReadFile(vnc_pwd_file)
635 except EnvironmentError, err:
636 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
637 (vnc_pwd_file, err))
638
639 config.write("vncpasswd = '%s'\n" % password.rstrip())
640
641 config.write("serial = 'pty'\n")
642 if hvp[constants.HV_USE_LOCALTIME]:
643 config.write("localtime = 1\n")
644
645 vif_data = []
646 nic_type = hvp[constants.HV_NIC_TYPE]
647 if nic_type is None:
648
649 nic_type_str = ", type=ioemu"
650 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
651 nic_type_str = ", type=paravirtualized"
652 else:
653 nic_type_str = ", model=%s, type=ioemu" % nic_type
654 for nic in instance.nics:
655 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
656 ip = getattr(nic, "ip", None)
657 if ip is not None:
658 nic_str += ", ip=%s" % ip
659 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
660 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
661 vif_data.append("'%s'" % nic_str)
662
663 config.write("vif = [%s]\n" % ",".join(vif_data))
664
665 disk_data = cls._GetConfigFileDiskData(block_devices,
666 hvp[constants.HV_BLOCKDEV_PREFIX])
667
668 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
669 if iso_path:
670 iso = "'file:%s,hdc:cdrom,r'" % iso_path
671 disk_data.append(iso)
672
673 config.write("disk = [%s]\n" % (",".join(disk_data)))
674
675 config.write("on_poweroff = 'destroy'\n")
676 if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
677 config.write("on_reboot = 'restart'\n")
678 else:
679 config.write("on_reboot = 'destroy'\n")
680 config.write("on_crash = 'restart'\n")
681
682 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
683 try:
684 utils.WriteFile("/etc/xen/%s" % instance.name,
685 data=config.getvalue())
686 except EnvironmentError, err:
687 raise errors.HypervisorError("Cannot write Xen instance confile"
688 " file /etc/xen/%s: %s" %
689 (instance.name, err))
690
691 return True
692