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
194
195 - def StopInstance(self, instance, force=False, retry=False, name=None):
196 """Stop an instance.
197
198 """
199 if name is None:
200 name = instance.name
201 self._RemoveConfigFile(name)
202 if force:
203 command = ["xm", "destroy", name]
204 else:
205 command = ["xm", "shutdown", name]
206 result = utils.RunCmd(command)
207
208 if result.failed:
209 raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
210 (name, result.fail_reason, result.output))
211
237
238 try:
239 utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
240 self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
241 except utils.RetryTimeout:
242 raise errors.HypervisorError("Failed to reboot instance %s: instance"
243 " did not reboot in the expected interval" %
244 (instance.name, ))
245
247 """Return information about the node.
248
249 @return: a dict with the following keys (memory values in MiB):
250 - memory_total: the total memory size on the node
251 - memory_free: the available memory on the node for instances
252 - memory_dom0: the memory used by the node itself, if available
253 - nr_cpus: total number of CPUs
254 - nr_nodes: in a NUMA system, the number of domains
255 - nr_sockets: the number of physical CPU sockets in the node
256
257 """
258
259 result = utils.RunCmd(["xm", "info"])
260 if result.failed:
261 logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
262 result.output)
263 return None
264
265 xmoutput = result.stdout.splitlines()
266 result = {}
267 cores_per_socket = threads_per_core = nr_cpus = None
268 for line in xmoutput:
269 splitfields = line.split(":", 1)
270
271 if len(splitfields) > 1:
272 key = splitfields[0].strip()
273 val = splitfields[1].strip()
274 if key == 'memory' or key == 'total_memory':
275 result['memory_total'] = int(val)
276 elif key == 'free_memory':
277 result['memory_free'] = int(val)
278 elif key == 'nr_cpus':
279 nr_cpus = result['cpu_total'] = int(val)
280 elif key == 'nr_nodes':
281 result['cpu_nodes'] = int(val)
282 elif key == 'cores_per_socket':
283 cores_per_socket = int(val)
284 elif key == 'threads_per_core':
285 threads_per_core = int(val)
286
287 if (cores_per_socket is not None and
288 threads_per_core is not None and nr_cpus is not None):
289 result['cpu_sockets'] = nr_cpus / (cores_per_socket * threads_per_core)
290
291 dom0_info = self.GetInstanceInfo("Domain-0")
292 if dom0_info is not None:
293 result['memory_dom0'] = dom0_info[2]
294
295 return result
296
297 @classmethod
307
309 """Verify the hypervisor.
310
311 For Xen, this verifies that the xend process is running.
312
313 """
314 result = utils.RunCmd(["xm", "info"])
315 if result.failed:
316 return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
317
318 @staticmethod
320 """Get disk directive for xen config file.
321
322 This method builds the xen config disk directive according to the
323 given disk_template and block_devices.
324
325 @param block_devices: list of tuples (cfdev, rldev):
326 - cfdev: dict containing ganeti config disk part
327 - rldev: ganeti.bdev.BlockDev object
328 @param blockdev_prefix: a string containing blockdevice prefix,
329 e.g. "sd" for /dev/sda
330
331 @return: string containing disk directive for xen instance config file
332
333 """
334 FILE_DRIVER_MAP = {
335 constants.FD_LOOP: "file",
336 constants.FD_BLKTAP: "tap:aio",
337 }
338 disk_data = []
339 if len(block_devices) > 24:
340
341 raise errors.HypervisorError("Too many disks")
342 namespace = [blockdev_prefix + chr(i + ord('a')) for i in range(24)]
343 for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
344 if cfdev.mode == constants.DISK_RDWR:
345 mode = "w"
346 else:
347 mode = "r"
348 if cfdev.dev_type == constants.LD_FILE:
349 line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
350 dev_path, sd_name, mode)
351 else:
352 line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
353 disk_data.append(line)
354
355 return disk_data
356
358 """Get instance information to perform a migration.
359
360 @type instance: L{objects.Instance}
361 @param instance: instance to be migrated
362 @rtype: string
363 @return: content of the xen config file
364
365 """
366 return self._ReadConfigFile(instance.name)
367
369 """Prepare to accept an instance.
370
371 @type instance: L{objects.Instance}
372 @param instance: instance to be accepted
373 @type info: string
374 @param info: content of the xen config file on the source node
375 @type target: string
376 @param target: target host (usually ip), on this node
377
378 """
379 pass
380
382 """Finalize an instance migration.
383
384 After a successful migration we write the xen config file.
385 We do nothing on a failure, as we did not change anything at accept time.
386
387 @type instance: L{objects.Instance}
388 @param instance: instance whose migration is being finalized
389 @type info: string
390 @param info: content of the xen config file on the source node
391 @type success: boolean
392 @param success: whether the migration was a success or a failure
393
394 """
395 if success:
396 self._WriteConfigFileStatic(instance.name, info)
397
399 """Migrate an instance to a target node.
400
401 The migration will not be attempted if the instance is not
402 currently running.
403
404 @type instance: L{objects.Instance}
405 @param instance: the instance to be migrated
406 @type target: string
407 @param target: ip address of the target node
408 @type live: boolean
409 @param live: perform a live migration
410
411 """
412 if self.GetInstanceInfo(instance.name) is None:
413 raise errors.HypervisorError("Instance not running, cannot migrate")
414
415 port = instance.hvparams[constants.HV_MIGRATION_PORT]
416
417 if not netutils.TcpPing(target, port, live_port_needed=True):
418 raise errors.HypervisorError("Remote host %s not listening on port"
419 " %s, cannot migrate" % (target, port))
420
421 args = ["xm", "migrate", "-p", "%d" % port]
422 if live:
423 args.append("-l")
424 args.extend([instance.name, target])
425 result = utils.RunCmd(args)
426 if result.failed:
427 raise errors.HypervisorError("Failed to migrate instance %s: %s" %
428 (instance.name, result.output))
429
430 try:
431 self._RemoveConfigFile(instance.name)
432 except EnvironmentError:
433 logging.exception("Failure while removing instance config file")
434
435 @classmethod
437 """Xen-specific powercycle.
438
439 This first does a Linux reboot (which triggers automatically a Xen
440 reboot), and if that fails it tries to do a Xen reboot. The reason
441 we don't try a Xen reboot first is that the xen reboot launches an
442 external command which connects to the Xen hypervisor, and that
443 won't work in case the root filesystem is broken and/or the xend
444 daemon is not working.
445
446 """
447 try:
448 cls.LinuxPowercycle()
449 finally:
450 utils.RunCmd(["xm", "debug", "R"])
451
454 """Xen PVM hypervisor interface"""
455
456 PARAMETERS = {
457 constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
458 constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
459 constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
460 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
461 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
462 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
463 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
464 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
465 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
466
467 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
468 }
469
470 @classmethod
472 """Write the Xen config file for the instance.
473
474 """
475 hvp = instance.hvparams
476 config = StringIO()
477 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
478
479
480
481 if hvp[constants.HV_USE_BOOTLOADER]:
482
483 bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
484 if bootloader_path:
485 config.write("bootloader = '%s'\n" % bootloader_path)
486 else:
487 raise errors.HypervisorError("Bootloader enabled, but missing"
488 " bootloader path")
489
490 bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
491 if bootloader_args:
492 config.write("bootargs = '%s'\n" % bootloader_args)
493 else:
494
495 kpath = hvp[constants.HV_KERNEL_PATH]
496 config.write("kernel = '%s'\n" % kpath)
497
498
499 initrd_path = hvp[constants.HV_INITRD_PATH]
500 if initrd_path:
501 config.write("ramdisk = '%s'\n" % initrd_path)
502
503
504 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
505 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
506 config.write("name = '%s'\n" % instance.name)
507
508 vif_data = []
509 for nic in instance.nics:
510 nic_str = "mac=%s" % (nic.mac)
511 ip = getattr(nic, "ip", None)
512 if ip is not None:
513 nic_str += ", ip=%s" % ip
514 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
515 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
516 vif_data.append("'%s'" % nic_str)
517
518 disk_data = cls._GetConfigFileDiskData(block_devices,
519 hvp[constants.HV_BLOCKDEV_PREFIX])
520
521 config.write("vif = [%s]\n" % ",".join(vif_data))
522 config.write("disk = [%s]\n" % ",".join(disk_data))
523
524 if hvp[constants.HV_ROOT_PATH]:
525 config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
526 config.write("on_poweroff = 'destroy'\n")
527 config.write("on_reboot = 'restart'\n")
528 config.write("on_crash = 'restart'\n")
529 config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
530
531 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
532 try:
533 utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
534 except EnvironmentError, err:
535 raise errors.HypervisorError("Cannot write Xen instance confile"
536 " file /etc/xen/%s: %s" %
537 (instance.name, err))
538
539 return True
540
543 """Xen HVM hypervisor interface"""
544
545 ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
546 constants.VNC_PASSWORD_FILE,
547 ]
548
549 PARAMETERS = {
550 constants.HV_ACPI: hv_base.NO_CHECK,
551 constants.HV_BOOT_ORDER: (True, ) +
552 (lambda x: x and len(x.strip("acdn")) == 0,
553 "Invalid boot order specified, must be one or more of [acdn]",
554 None, None),
555 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
556 constants.HV_DISK_TYPE:
557 hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
558 constants.HV_NIC_TYPE:
559 hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
560 constants.HV_PAE: hv_base.NO_CHECK,
561 constants.HV_VNC_BIND_ADDRESS:
562 (False, netutils.IP4Address.IsValid,
563 "VNC bind address is not a valid IP address", None, None),
564 constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
565 constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
566 constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
567 constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK,
568 constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
569 constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
570
571 constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
572 }
573
574 @classmethod
576 """Create a Xen 3.1 HVM config file.
577
578 """
579 hvp = instance.hvparams
580
581 config = StringIO()
582 config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
583
584
585 kpath = hvp[constants.HV_KERNEL_PATH]
586 config.write("kernel = '%s'\n" % kpath)
587
588 config.write("builder = 'hvm'\n")
589 config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
590 config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
591 config.write("name = '%s'\n" % instance.name)
592 if hvp[constants.HV_PAE]:
593 config.write("pae = 1\n")
594 else:
595 config.write("pae = 0\n")
596 if hvp[constants.HV_ACPI]:
597 config.write("acpi = 1\n")
598 else:
599 config.write("acpi = 0\n")
600 config.write("apic = 1\n")
601 config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
602 config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
603 config.write("sdl = 0\n")
604 config.write("usb = 1\n")
605 config.write("usbdevice = 'tablet'\n")
606 config.write("vnc = 1\n")
607 if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
608 config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
609 else:
610 config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
611
612 if instance.network_port > constants.VNC_BASE_PORT:
613 display = instance.network_port - constants.VNC_BASE_PORT
614 config.write("vncdisplay = %s\n" % display)
615 config.write("vncunused = 0\n")
616 else:
617 config.write("# vncdisplay = 1\n")
618 config.write("vncunused = 1\n")
619
620 vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
621 try:
622 password = utils.ReadFile(vnc_pwd_file)
623 except EnvironmentError, err:
624 raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
625 (vnc_pwd_file, err))
626
627 config.write("vncpasswd = '%s'\n" % password.rstrip())
628
629 config.write("serial = 'pty'\n")
630 if hvp[constants.HV_USE_LOCALTIME]:
631 config.write("localtime = 1\n")
632
633 vif_data = []
634 nic_type = hvp[constants.HV_NIC_TYPE]
635 if nic_type is None:
636
637 nic_type_str = ", type=ioemu"
638 elif nic_type == constants.HT_NIC_PARAVIRTUAL:
639 nic_type_str = ", type=paravirtualized"
640 else:
641 nic_type_str = ", model=%s, type=ioemu" % nic_type
642 for nic in instance.nics:
643 nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
644 ip = getattr(nic, "ip", None)
645 if ip is not None:
646 nic_str += ", ip=%s" % ip
647 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
648 nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
649 vif_data.append("'%s'" % nic_str)
650
651 config.write("vif = [%s]\n" % ",".join(vif_data))
652
653 disk_data = cls._GetConfigFileDiskData(block_devices,
654 hvp[constants.HV_BLOCKDEV_PREFIX])
655
656 iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
657 if iso_path:
658 iso = "'file:%s,hdc:cdrom,r'" % iso_path
659 disk_data.append(iso)
660
661 config.write("disk = [%s]\n" % (",".join(disk_data)))
662
663 config.write("on_poweroff = 'destroy'\n")
664 config.write("on_reboot = 'restart'\n")
665 config.write("on_crash = 'restart'\n")
666
667 utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
668 try:
669 utils.WriteFile("/etc/xen/%s" % instance.name,
670 data=config.getvalue())
671 except EnvironmentError, err:
672 raise errors.HypervisorError("Cannot write Xen instance confile"
673 " file /etc/xen/%s: %s" %
674 (instance.name, err))
675
676 return True
677