4 # Copyright (C) 2008 Google Inc.
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.
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.
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
32 from cStringIO import StringIO
34 from ganeti import utils
35 from ganeti import constants
36 from ganeti import errors
37 from ganeti import serializer
38 from ganeti import objects
39 from ganeti.hypervisor import hv_base
42 class KVMHypervisor(hv_base.BaseHypervisor):
43 """KVM hypervisor interface"""
45 _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
46 _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
47 _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
48 _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
49 _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
52 constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
53 constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
54 constants.HV_ROOT_PATH: hv_base.NO_CHECK,
55 constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
56 constants.HV_ACPI: hv_base.NO_CHECK,
57 constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
58 constants.HV_VNC_BIND_ADDRESS: \
59 (False, lambda x: (utils.IsValidIP(x) or utils.IsAbsNormPath(x)),
60 "the VNC bind address must be either a valid IP address or an absolute"
61 " pathname", None, None),
62 constants.HV_VNC_TLS: hv_base.NO_CHECK,
63 constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
64 constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
65 constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
66 constants.HV_BOOT_ORDER: \
67 hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
68 constants.HV_NIC_TYPE: \
69 hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
70 constants.HV_DISK_TYPE: \
71 hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
72 constants.HV_USB_MOUSE: \
73 hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
76 _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
79 _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
86 hv_base.BaseHypervisor.__init__(self)
87 # Let's make sure the directories we need exist, even if the RUN_DIR lives
88 # in a tmpfs filesystem or has been otherwise wiped out.
89 dirs = [(dir, constants.RUN_DIRS_MODE) for dir in self._DIRS]
90 utils.EnsureDirs(dirs)
92 def _InstancePidAlive(self, instance_name):
93 """Returns the instance pid and pidfile
96 pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
97 pid = utils.ReadPidFile(pidfile)
98 alive = utils.IsProcessAlive(pid)
100 return (pidfile, pid, alive)
103 def _InstanceMonitor(cls, instance_name):
104 """Returns the instance monitor socket name
107 return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
110 def _InstanceSerial(cls, instance_name):
111 """Returns the instance serial socket name
114 return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
117 def _InstanceKVMRuntime(cls, instance_name):
118 """Returns the instance KVM runtime filename
121 return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
124 def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
125 """Removes an instance's rutime sockets/files.
128 utils.RemoveFile(pidfile)
129 utils.RemoveFile(cls._InstanceMonitor(instance_name))
130 utils.RemoveFile(cls._InstanceSerial(instance_name))
131 utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
133 def _WriteNetScript(self, instance, seq, nic):
134 """Write a script to connect a net interface to the proper bridge.
136 This can be used by any qemu-type hypervisor.
138 @param instance: instance we're acting on
139 @type instance: instance object
140 @param seq: nic sequence number
142 @param nic: nic we're acting on
143 @type nic: nic object
144 @return: netscript file name
149 script.write("#!/bin/sh\n")
150 script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
151 script.write("export INSTANCE=%s\n" % instance.name)
152 script.write("export MAC=%s\n" % nic.mac)
153 script.write("export IP=%s\n" % nic.ip)
154 script.write("export BRIDGE=%s\n" % nic.bridge)
155 script.write("export INTERFACE=$1\n")
156 # TODO: make this configurable at ./configure time
157 script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
158 script.write(" # Execute the user-specific vif file\n")
159 script.write(" %s\n" % self._KVM_NETWORK_SCRIPT)
160 script.write("else\n")
161 script.write(" # Connect the interface to the bridge\n")
162 script.write(" /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
163 script.write(" /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
164 script.write("fi\n\n")
165 # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
166 # mounted noexec sometimes, so we'll have to find another place.
167 (tmpfd, tmpfile_name) = tempfile.mkstemp()
168 tmpfile = os.fdopen(tmpfd, 'w')
169 tmpfile.write(script.getvalue())
171 os.chmod(tmpfile_name, 0755)
174 def ListInstances(self):
175 """Get the list of running instances.
177 We can do this by listing our live instances directory and
178 checking whether the associated kvm process is still alive.
182 for name in os.listdir(self._PIDS_DIR):
183 filename = "%s/%s" % (self._PIDS_DIR, name)
184 if utils.IsProcessAlive(utils.ReadPidFile(filename)):
188 def GetInstanceInfo(self, instance_name):
189 """Get instance properties.
191 @param instance_name: the instance name
193 @return: tuple (name, id, memory, vcpus, stat, times)
196 pidfile, pid, alive = self._InstancePidAlive(instance_name)
200 cmdline_file = "/proc/%s/cmdline" % pid
202 fh = open(cmdline_file, 'r')
207 except EnvironmentError, err:
208 raise errors.HypervisorError("Failed to list instance %s: %s" %
209 (instance_name, err))
216 arg_list = cmdline.split('\x00')
218 arg = arg_list.pop(0)
220 memory = int(arg_list.pop(0))
222 vcpus = int(arg_list.pop(0))
224 return (instance_name, pid, memory, vcpus, stat, times)
226 def GetAllInstancesInfo(self):
227 """Get properties of all instances.
229 @return: list of tuples (name, id, memory, vcpus, stat, times)
233 for name in os.listdir(self._PIDS_DIR):
234 filename = "%s/%s" % (self._PIDS_DIR, name)
235 if utils.IsProcessAlive(utils.ReadPidFile(filename)):
237 info = self.GetInstanceInfo(name)
238 except errors.HypervisorError, err:
245 def _GenerateKVMRuntime(self, instance, block_devices):
246 """Generate KVM information to start an instance.
249 pidfile, pid, alive = self._InstancePidAlive(instance.name)
250 kvm = constants.KVM_PATH
252 kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
253 kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
254 kvm_cmd.extend(['-pidfile', pidfile])
255 # used just by the vnc server, if enabled
256 kvm_cmd.extend(['-name', instance.name])
257 kvm_cmd.extend(['-daemonize'])
258 if not instance.hvparams[constants.HV_ACPI]:
259 kvm_cmd.extend(['-no-acpi'])
261 hvp = instance.hvparams
262 boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
263 boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
264 boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
267 kvm_cmd.extend(['-boot', 'n'])
269 disk_type = hvp[constants.HV_DISK_TYPE]
270 if disk_type == constants.HT_DISK_PARAVIRTUAL:
271 if_val = ',if=virtio'
273 if_val = ',if=%s' % disk_type
274 for cfdev, dev_path in block_devices:
275 if cfdev.mode != constants.DISK_RDWR:
276 raise errors.HypervisorError("Instance has read-only disks which"
277 " are not supported by KVM")
278 # TODO: handle FD_LOOP and FD_BLKTAP (?)
280 kvm_cmd.extend(['-boot', 'c'])
281 boot_val = ',boot=on'
282 # We only boot from the first disk
287 drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
288 kvm_cmd.extend(['-drive', drive_val])
290 iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
292 options = ',format=raw,media=cdrom'
294 kvm_cmd.extend(['-boot', 'd'])
295 options = '%s,boot=on' % options
297 options = '%s,if=virtio' % options
298 drive_val = 'file=%s%s' % (iso_image, options)
299 kvm_cmd.extend(['-drive', drive_val])
301 kernel_path = hvp[constants.HV_KERNEL_PATH]
303 kvm_cmd.extend(['-kernel', kernel_path])
304 initrd_path = hvp[constants.HV_INITRD_PATH]
306 kvm_cmd.extend(['-initrd', initrd_path])
307 root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
308 hvp[constants.HV_KERNEL_ARGS]]
309 if hvp[constants.HV_SERIAL_CONSOLE]:
310 root_append.append('console=ttyS0,38400')
311 kvm_cmd.extend(['-append', ' '.join(root_append)])
313 mouse_type = hvp[constants.HV_USB_MOUSE]
315 kvm_cmd.extend(['-usb'])
316 kvm_cmd.extend(['-usbdevice', mouse_type])
318 # FIXME: handle vnc password
319 vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
321 if utils.IsValidIP(vnc_bind_address):
322 if instance.network_port > constants.VNC_BASE_PORT:
323 display = instance.network_port - constants.VNC_BASE_PORT
324 if vnc_bind_address == '0.0.0.0':
325 vnc_arg = ':%d' % (display)
327 vnc_arg = '%s:%d' % (vnc_bind_address, display)
329 logging.error("Network port is not a valid VNC display (%d < %d)."
330 " Not starting VNC" %
331 (instance.network_port,
332 constants.VNC_BASE_PORT))
335 # Only allow tls and other option when not binding to a file, for now.
336 # kvm/qemu gets confused otherwise about the filename to use.
338 if hvp[constants.HV_VNC_TLS]:
339 vnc_append = '%s,tls' % vnc_append
340 if hvp[constants.HV_VNC_X509_VERIFY]:
341 vnc_append = '%s,x509verify=%s' % (vnc_append,
342 hvp[constants.HV_VNC_X509])
343 elif hvp[constants.HV_VNC_X509]:
344 vnc_append = '%s,x509=%s' % (vnc_append,
345 hvp[constants.HV_VNC_X509])
346 vnc_arg = '%s%s' % (vnc_arg, vnc_append)
349 vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
351 kvm_cmd.extend(['-vnc', vnc_arg])
353 kvm_cmd.extend(['-nographic'])
355 monitor_dev = 'unix:%s,server,nowait' % \
356 self._InstanceMonitor(instance.name)
357 kvm_cmd.extend(['-monitor', monitor_dev])
358 if hvp[constants.HV_SERIAL_CONSOLE]:
359 serial_dev = ('unix:%s,server,nowait' %
360 self._InstanceSerial(instance.name))
361 kvm_cmd.extend(['-serial', serial_dev])
363 kvm_cmd.extend(['-serial', 'none'])
365 # Save the current instance nics, but defer their expansion as parameters,
366 # as we'll need to generate executable temp files for them.
367 kvm_nics = instance.nics
370 return (kvm_cmd, kvm_nics, hvparams)
372 def _WriteKVMRuntime(self, instance_name, data):
373 """Write an instance's KVM runtime
377 utils.WriteFile(self._InstanceKVMRuntime(instance_name),
379 except EnvironmentError, err:
380 raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
382 def _ReadKVMRuntime(self, instance_name):
383 """Read an instance's KVM runtime
387 file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
388 except EnvironmentError, err:
389 raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
392 def _SaveKVMRuntime(self, instance, kvm_runtime):
393 """Save an instance's KVM runtime
396 kvm_cmd, kvm_nics, hvparams = kvm_runtime
397 serialized_nics = [nic.ToDict() for nic in kvm_nics]
398 serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
399 self._WriteKVMRuntime(instance.name, serialized_form)
401 def _LoadKVMRuntime(self, instance, serialized_runtime=None):
402 """Load an instance's KVM runtime
405 if not serialized_runtime:
406 serialized_runtime = self._ReadKVMRuntime(instance.name)
407 loaded_runtime = serializer.Load(serialized_runtime)
408 kvm_cmd, serialized_nics, hvparams = loaded_runtime
409 kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
410 return (kvm_cmd, kvm_nics, hvparams)
412 def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
413 """Execute a KVM cmd, after completing it with some last minute data
415 @type incoming: tuple of strings
416 @param incoming: (target_host_ip, port)
419 pidfile, pid, alive = self._InstancePidAlive(instance.name)
421 raise errors.HypervisorError("Failed to start instance %s: %s" %
422 (instance.name, "already running"))
426 kvm_cmd, kvm_nics, hvparams = kvm_runtime
429 kvm_cmd.extend(['-net', 'none'])
431 nic_type = hvparams[constants.HV_NIC_TYPE]
432 if nic_type == constants.HT_NIC_PARAVIRTUAL:
433 nic_model = "model=virtio"
435 nic_model = "model=%s" % nic_type
437 for nic_seq, nic in enumerate(kvm_nics):
438 nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
439 script = self._WriteNetScript(instance, nic_seq, nic)
440 kvm_cmd.extend(['-net', nic_val])
441 kvm_cmd.extend(['-net', 'tap,script=%s' % script])
442 temp_files.append(script)
445 target, port = incoming
446 kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
448 result = utils.RunCmd(kvm_cmd)
450 raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
451 (instance.name, result.fail_reason,
454 if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
455 raise errors.HypervisorError("Failed to start instance %s: %s" %
458 for filename in temp_files:
459 utils.RemoveFile(filename)
461 def StartInstance(self, instance, block_devices):
462 """Start an instance.
465 pidfile, pid, alive = self._InstancePidAlive(instance.name)
467 raise errors.HypervisorError("Failed to start instance %s: %s" %
468 (instance.name, "already running"))
470 kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
471 self._SaveKVMRuntime(instance, kvm_runtime)
472 self._ExecuteKVMRuntime(instance, kvm_runtime)
474 def _CallMonitorCommand(self, instance_name, command):
475 """Invoke a command on the instance monitor.
478 socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
479 (utils.ShellQuote(command),
480 constants.SOCAT_PATH,
481 utils.ShellQuote(self._InstanceMonitor(instance_name))))
482 result = utils.RunCmd(socat)
484 msg = ("Failed to send command '%s' to instance %s."
485 " output: %s, error: %s, fail_reason: %s" %
486 (command, instance_name,
487 result.stdout, result.stderr, result.fail_reason))
488 raise errors.HypervisorError(msg)
492 def _RetryInstancePowerdown(self, instance, pid, timeout=30):
493 """Wait for an instance to power down.
496 # Wait up to $timeout seconds
497 end = time.time() + timeout
499 while time.time() < end and utils.IsProcessAlive(pid):
500 self._CallMonitorCommand(instance.name, 'system_powerdown')
502 # Make wait time longer for next try
506 def StopInstance(self, instance, force=False):
510 pidfile, pid, alive = self._InstancePidAlive(instance.name)
511 if pid > 0 and alive:
512 if force or not instance.hvparams[constants.HV_ACPI]:
513 utils.KillProcess(pid)
515 self._RetryInstancePowerdown(instance, pid)
517 if not utils.IsProcessAlive(pid):
518 self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
523 def RebootInstance(self, instance):
524 """Reboot an instance.
527 # For some reason if we do a 'send-key ctrl-alt-delete' to the control
528 # socket the instance will stop, but now power up again. So we'll resort
529 # to shutdown and restart.
530 pidfile, pid, alive = self._InstancePidAlive(instance.name)
532 raise errors.HypervisorError("Failed to reboot instance %s: not running" %
534 # StopInstance will delete the saved KVM runtime so:
535 # ...first load it...
536 kvm_runtime = self._LoadKVMRuntime(instance)
537 # ...now we can safely call StopInstance...
538 if not self.StopInstance(instance):
539 self.StopInstance(instance, force=True)
540 # ...and finally we can save it again, and execute it...
541 self._SaveKVMRuntime(instance, kvm_runtime)
542 self._ExecuteKVMRuntime(instance, kvm_runtime)
544 def MigrationInfo(self, instance):
545 """Get instance information to perform a migration.
547 @type instance: L{objects.Instance}
548 @param instance: instance to be migrated
550 @return: content of the KVM runtime file
553 return self._ReadKVMRuntime(instance.name)
555 def AcceptInstance(self, instance, info, target):
556 """Prepare to accept an instance.
558 @type instance: L{objects.Instance}
559 @param instance: instance to be accepted
561 @param info: content of the KVM runtime file on the source node
563 @param target: target host (usually ip), on this node
566 kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
567 incoming_address = (target, constants.KVM_MIGRATION_PORT)
568 self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
570 def FinalizeMigration(self, instance, info, success):
571 """Finalize an instance migration.
573 Stop the incoming mode KVM.
575 @type instance: L{objects.Instance}
576 @param instance: instance whose migration is being aborted
580 self._WriteKVMRuntime(instance.name, info)
582 self.StopInstance(instance, force=True)
584 def MigrateInstance(self, instance_name, target, live):
585 """Migrate an instance to a target node.
587 The migration will not be attempted if the instance is not
590 @type instance_name: string
591 @param instance_name: name of the instance to be migrated
593 @param target: ip address of the target node
595 @param live: perform a live migration
598 pidfile, pid, alive = self._InstancePidAlive(instance_name)
600 raise errors.HypervisorError("Instance not running, cannot migrate")
603 self._CallMonitorCommand(instance_name, 'stop')
605 migrate_command = ('migrate -d tcp:%s:%s' %
606 (target, constants.KVM_MIGRATION_PORT))
607 self._CallMonitorCommand(instance_name, migrate_command)
609 info_command = 'info migrate'
612 result = self._CallMonitorCommand(instance_name, info_command)
613 match = self._MIGRATION_STATUS_RE.search(result.stdout)
615 raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
618 status = match.group(1)
619 if status == 'completed':
621 elif status == 'active':
623 elif status == 'failed' or status == 'cancelled':
625 self._CallMonitorCommand(instance_name, 'cont')
626 raise errors.HypervisorError("Migration %s at the kvm level" %
629 logging.info("KVM: unknown migration status '%s'" % status)
632 utils.KillProcess(pid)
633 self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
635 def GetNodeInfo(self):
636 """Return information about the node.
638 This is just a wrapper over the base GetLinuxNodeInfo method.
640 @return: a dict with the following keys (values in MiB):
641 - memory_total: the total memory size on the node
642 - memory_free: the available memory on the node for instances
643 - memory_dom0: the memory used by the node itself, if available
646 return self.GetLinuxNodeInfo()
649 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
650 """Return a command for connecting to the console of an instance.
653 if hvparams[constants.HV_SERIAL_CONSOLE]:
654 # FIXME: The socat shell is not perfect. In particular the way we start
655 # it ctrl+c will close it, rather than being passed to the other end.
656 # On the other hand if we pass the option 'raw' (or ignbrk=1) there
657 # will be no way of exiting socat (except killing it from another shell)
658 # and ctrl+c doesn't work anyway, printing ^C rather than being
659 # interpreted by kvm. For now we'll leave it this way, which at least
660 # allows a minimal interaction and changes on the machine.
661 shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
662 (constants.SOCAT_PATH,
663 utils.ShellQuote(cls._InstanceSerial(instance.name))))
665 shell_command = "echo 'No serial shell for instance %s'" % instance.name
667 vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
669 if instance.network_port > constants.VNC_BASE_PORT:
670 display = instance.network_port - constants.VNC_BASE_PORT
671 vnc_command = ("echo 'Instance has VNC listening on %s:%d"
672 " (display: %d)'" % (vnc_bind_address,
673 instance.network_port,
675 shell_command = "%s; %s" % (vnc_command, shell_command)
680 """Verify the hypervisor.
682 Check that the binary exists.
685 if not os.path.exists(constants.KVM_PATH):
686 return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
687 if not os.path.exists(constants.SOCAT_PATH):
688 return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
692 def CheckParameterSyntax(cls, hvparams):
693 """Check the given parameters for validity.
696 @param hvparams: dictionary with parameter names/value
697 @raise errors.HypervisorError: when a parameter is not valid
700 super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
702 kernel_path = hvparams[constants.HV_KERNEL_PATH]
704 if not hvparams[constants.HV_ROOT_PATH]:
705 raise errors.HypervisorError("Need a root partition for the instance,"
706 " if a kernel is defined")
708 if (hvparams[constants.HV_VNC_X509_VERIFY] and
709 not hvparams[constants.HV_VNC_X509]):
710 raise errors.HypervisorError("%s must be defined, if %s is" %
711 (constants.HV_VNC_X509,
712 constants.HV_VNC_X509_VERIFY))
714 boot_order = hvparams[constants.HV_BOOT_ORDER]
716 if (boot_order == constants.HT_BO_CDROM and
717 not hvparams[constants.HV_CDROM_IMAGE_PATH]):
718 raise errors.HypervisorError("Cannot boot from cdrom without an"
720 if (boot_order == constants.HT_BO_NETWORK and
721 hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
722 raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
723 " change the NIC type.")
726 def PowercycleNode(cls):
727 """KVM powercycle, just a wrapper over Linux powercycle.
730 cls.LinuxPowercycle()