| import os, logging, time, glob, re |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.bin import utils |
| import virt_utils |
| |
| class VMError(Exception): |
| pass |
| |
| |
| class VMCreateError(VMError): |
| def __init__(self, cmd, status, output): |
| VMError.__init__(self, cmd, status, output) |
| self.cmd = cmd |
| self.status = status |
| self.output = output |
| |
| def __str__(self): |
| return ("VM creation command failed: %r (status: %s, " |
| "output: %r)" % (self.cmd, self.status, self.output)) |
| |
| |
| class VMHashMismatchError(VMError): |
| def __init__(self, actual, expected): |
| VMError.__init__(self, actual, expected) |
| self.actual_hash = actual |
| self.expected_hash = expected |
| |
| def __str__(self): |
| return ("CD image hash (%s) differs from expected one (%s)" % |
| (self.actual_hash, self.expected_hash)) |
| |
| |
| class VMImageMissingError(VMError): |
| def __init__(self, filename): |
| VMError.__init__(self, filename) |
| self.filename = filename |
| |
| def __str__(self): |
| return "CD image file not found: %r" % self.filename |
| |
| |
| class VMImageCheckError(VMError): |
| def __init__(self, filename): |
| VMError.__init__(self, filename) |
| self.filename = filename |
| |
| def __str__(self): |
| return "Errors found on image: %r" % self.filename |
| |
| |
| class VMBadPATypeError(VMError): |
| def __init__(self, pa_type): |
| VMError.__init__(self, pa_type) |
| self.pa_type = pa_type |
| |
| def __str__(self): |
| return "Unsupported PCI assignable type: %r" % self.pa_type |
| |
| |
| class VMPAError(VMError): |
| def __init__(self, pa_type): |
| VMError.__init__(self, pa_type) |
| self.pa_type = pa_type |
| |
| def __str__(self): |
| return ("No PCI assignable devices could be assigned " |
| "(pci_assignable=%r)" % self.pa_type) |
| |
| |
| class VMPostCreateError(VMError): |
| def __init__(self, cmd, output): |
| VMError.__init__(self, cmd, output) |
| self.cmd = cmd |
| self.output = output |
| |
| |
| class VMHugePageError(VMPostCreateError): |
| def __str__(self): |
| return ("Cannot allocate hugepage memory (command: %r, " |
| "output: %r)" % (self.cmd, self.output)) |
| |
| |
| class VMKVMInitError(VMPostCreateError): |
| def __str__(self): |
| return ("Cannot initialize KVM (command: %r, output: %r)" % |
| (self.cmd, self.output)) |
| |
| |
| class VMDeadError(VMError): |
| def __init__(self, reason='', detail=''): |
| VMError.__init__(self) |
| self.reason = reason |
| self.detail = detail |
| |
| def __str__(self): |
| msg = "VM is dead" |
| if self.reason: |
| msg += " reason: %s" % self.reason |
| if self.detail: |
| msg += " detail: %r" % self.detail |
| return (msg) |
| |
| |
| class VMDeadKernelCrashError(VMError): |
| def __init__(self, kernel_crash): |
| VMError.__init__(self, kernel_crash) |
| self.kernel_crash = kernel_crash |
| |
| def __str__(self): |
| return ("VM is dead due to a kernel crash:\n%s" % self.kernel_crash) |
| |
| |
| class VMAddressError(VMError): |
| pass |
| |
| |
| class VMPortNotRedirectedError(VMAddressError): |
| def __init__(self, port): |
| VMAddressError.__init__(self, port) |
| self.port = port |
| |
| def __str__(self): |
| return "Port not redirected: %s" % self.port |
| |
| |
| class VMAddressVerificationError(VMAddressError): |
| def __init__(self, mac, ip): |
| VMAddressError.__init__(self, mac, ip) |
| self.mac = mac |
| self.ip = ip |
| |
| def __str__(self): |
| return ("Cannot verify MAC-IP address mapping using arping: " |
| "%s ---> %s" % (self.mac, self.ip)) |
| |
| |
| class VMMACAddressMissingError(VMAddressError): |
| def __init__(self, nic_index): |
| VMAddressError.__init__(self, nic_index) |
| self.nic_index = nic_index |
| |
| def __str__(self): |
| return "No MAC address defined for NIC #%s" % self.nic_index |
| |
| |
| class VMIPAddressMissingError(VMAddressError): |
| def __init__(self, mac): |
| VMAddressError.__init__(self, mac) |
| self.mac = mac |
| |
| def __str__(self): |
| return "Cannot find IP address for MAC address %s" % self.mac |
| |
| |
| class VMMigrateError(VMError): |
| pass |
| |
| |
| class VMMigrateTimeoutError(VMMigrateError): |
| pass |
| |
| |
| class VMMigrateCancelError(VMMigrateError): |
| pass |
| |
| |
| class VMMigrateFailedError(VMMigrateError): |
| pass |
| |
| class VMMigrateProtoUnsupportedError(VMMigrateError): |
| pass |
| |
| |
| class VMMigrateStateMismatchError(VMMigrateError): |
| def __init__(self, src_hash, dst_hash): |
| VMMigrateError.__init__(self, src_hash, dst_hash) |
| self.src_hash = src_hash |
| self.dst_hash = dst_hash |
| |
| def __str__(self): |
| return ("Mismatch of VM state before and after migration (%s != %s)" % |
| (self.src_hash, self.dst_hash)) |
| |
| |
| class VMRebootError(VMError): |
| pass |
| |
| class VMStatusError(VMError): |
| pass |
| |
| def get_image_filename(params, root_dir): |
| """ |
| Generate an image path from params and root_dir. |
| |
| @param params: Dictionary containing the test parameters. |
| @param root_dir: Base directory for relative filenames. |
| |
| @note: params should contain: |
| image_name -- the name of the image file, without extension |
| image_format -- the format of the image (qcow2, raw etc) |
| """ |
| image_name = params.get("image_name", "image") |
| image_format = params.get("image_format", "qcow2") |
| if params.get("image_raw_device") == "yes": |
| return image_name |
| image_filename = "%s.%s" % (image_name, image_format) |
| image_filename = virt_utils.get_path(root_dir, image_filename) |
| return image_filename |
| |
| |
| def create_image(params, root_dir): |
| """ |
| Create an image using qemu_image. |
| |
| @param params: Dictionary containing the test parameters. |
| @param root_dir: Base directory for relative filenames. |
| |
| @note: params should contain: |
| image_name -- the name of the image file, without extension |
| image_format -- the format of the image (qcow2, raw etc) |
| image_cluster_size (optional) -- the cluster size for the image |
| image_size -- the requested size of the image (a string |
| qemu-img can understand, such as '10G') |
| """ |
| qemu_img_cmd = virt_utils.get_path(root_dir, params.get("qemu_img_binary", |
| "qemu-img")) |
| qemu_img_cmd += " create" |
| |
| format = params.get("image_format", "qcow2") |
| qemu_img_cmd += " -f %s" % format |
| |
| image_cluster_size = params.get("image_cluster_size", None) |
| if image_cluster_size is not None: |
| qemu_img_cmd += " -o cluster_size=%s" % image_cluster_size |
| |
| image_filename = get_image_filename(params, root_dir) |
| qemu_img_cmd += " %s" % image_filename |
| |
| size = params.get("image_size", "10G") |
| qemu_img_cmd += " %s" % size |
| |
| utils.system(qemu_img_cmd) |
| return image_filename |
| |
| |
| def remove_image(params, root_dir): |
| """ |
| Remove an image file. |
| |
| @param params: A dict |
| @param root_dir: Base directory for relative filenames. |
| |
| @note: params should contain: |
| image_name -- the name of the image file, without extension |
| image_format -- the format of the image (qcow2, raw etc) |
| """ |
| image_filename = get_image_filename(params, root_dir) |
| logging.debug("Removing image file %s", image_filename) |
| if os.path.exists(image_filename): |
| os.unlink(image_filename) |
| else: |
| logging.debug("Image file %s not found") |
| |
| |
| def check_image(params, root_dir): |
| """ |
| Check an image using the appropriate tools for each virt backend. |
| |
| @param params: Dictionary containing the test parameters. |
| @param root_dir: Base directory for relative filenames. |
| |
| @note: params should contain: |
| image_name -- the name of the image file, without extension |
| image_format -- the format of the image (qcow2, raw etc) |
| |
| @raise VMImageCheckError: In case qemu-img check fails on the image. |
| """ |
| vm_type = params.get("vm_type") |
| if vm_type == 'kvm': |
| image_filename = get_image_filename(params, root_dir) |
| logging.debug("Checking image file %s", image_filename) |
| qemu_img_cmd = virt_utils.get_path(root_dir, |
| params.get("qemu_img_binary", "qemu-img")) |
| image_is_qcow2 = params.get("image_format") == 'qcow2' |
| if os.path.exists(image_filename) and image_is_qcow2: |
| # Verifying if qemu-img supports 'check' |
| q_result = utils.run(qemu_img_cmd, ignore_status=True) |
| q_output = q_result.stdout |
| check_img = True |
| if not "check" in q_output: |
| logging.error("qemu-img does not support 'check', " |
| "skipping check") |
| check_img = False |
| if not "info" in q_output: |
| logging.error("qemu-img does not support 'info', " |
| "skipping check") |
| check_img = False |
| if check_img: |
| try: |
| utils.system("%s info %s" % (qemu_img_cmd, image_filename)) |
| except error.CmdError: |
| logging.error("Error getting info from image %s", |
| image_filename) |
| |
| cmd_result = utils.run("%s check %s" % |
| (qemu_img_cmd, image_filename), |
| ignore_status=True) |
| # Error check, large chances of a non-fatal problem. |
| # There are chances that bad data was skipped though |
| if cmd_result.exit_status == 1: |
| for e_line in cmd_result.stdout.splitlines(): |
| logging.error("[stdout] %s", e_line) |
| for e_line in cmd_result.stderr.splitlines(): |
| logging.error("[stderr] %s", e_line) |
| raise error.TestWarn("qemu-img check error. Some bad data " |
| "in the image may have gone unnoticed") |
| # Exit status 2 is data corruption for sure, so fail the test |
| elif cmd_result.exit_status == 2: |
| for e_line in cmd_result.stdout.splitlines(): |
| logging.error("[stdout] %s", e_line) |
| for e_line in cmd_result.stderr.splitlines(): |
| logging.error("[stderr] %s", e_line) |
| raise VMImageCheckError(image_filename) |
| # Leaked clusters, they are known to be harmless to data |
| # integrity |
| elif cmd_result.exit_status == 3: |
| raise error.TestWarn("Leaked clusters were noticed during " |
| "image check. No data integrity " |
| "problem was found though.") |
| |
| else: |
| if not os.path.exists(image_filename): |
| logging.debug("Image file %s not found, skipping check", |
| image_filename) |
| elif not image_is_qcow2: |
| logging.debug("Image file %s not qcow2, skipping check", |
| image_filename) |
| |
| |
| class BaseVM(object): |
| """ |
| Base class for all hypervisor specific VM subclasses. |
| |
| This class should not be used directly, that is, do not attempt to |
| instantiate and use this class. Instead, one should implement a subclass |
| that implements, at the very least, all methods defined right after the |
| the comment blocks that are marked with: |
| |
| "Public API - *must* be reimplemented with virt specific code" |
| |
| and |
| |
| "Protected API - *must* be reimplemented with virt specific classes" |
| |
| The current proposal regarding methods naming convention is: |
| |
| - Public API methods: named in the usual way, consumed by tests |
| - Protected API methods: name begins with a single underline, to be |
| consumed only by BaseVM and subclasses |
| - Private API methods: name begins with double underline, to be consumed |
| only by the VM subclass itself (usually implements virt specific |
| functionality: example: __make_qemu_command()) |
| |
| So called "protected" methods are intended to be used only by VM classes, |
| and not be consumed by tests. Theses should respect a naming convention |
| and always be preceeded by a single underline. |
| |
| Currently most (if not all) methods are public and appears to be consumed |
| by tests. It is a ongoing task to determine whether methods should be |
| "public" or "protected". |
| """ |
| |
| # |
| # Assuming that all low-level hypervisor have at least migration via tcp |
| # (true for xen & kvm). Also true for libvirt (using xen and kvm drivers) |
| # |
| MIGRATION_PROTOS = ['tcp', ] |
| |
| def __init__(self, name, params): |
| self.name = name |
| self.params = params |
| |
| # |
| # Assuming all low-level hypervisors will have a serial (like) console |
| # connection to the guest. libvirt also supports serial (like) consoles |
| # (virDomainOpenConsole). subclasses should set this to an object that |
| # is or behaves like aexpect.ShellSession. |
| # |
| self.serial_console = None |
| |
| self._generate_unique_id() |
| |
| |
| def _generate_unique_id(self): |
| """ |
| Generate a unique identifier for this VM |
| """ |
| while True: |
| self.instance = (time.strftime("%Y%m%d-%H%M%S-") + |
| virt_utils.generate_random_string(4)) |
| if not glob.glob("/tmp/*%s" % self.instance): |
| break |
| |
| |
| # |
| # Public API - could be reimplemented with virt specific code |
| # |
| def verify_alive(self): |
| """ |
| Make sure the VM is alive and that the main monitor is responsive. |
| |
| Can be subclassed to provide better information on why the VM is |
| not alive (reason, detail) |
| |
| @raise VMDeadError: If the VM is dead |
| @raise: Various monitor exceptions if the monitor is unresponsive |
| """ |
| if self.is_dead(): |
| raise VMDeadError |
| |
| |
| def get_mac_address(self, nic_index=0): |
| """ |
| Return the MAC address of a NIC. |
| |
| @param nic_index: Index of the NIC |
| @raise VMMACAddressMissingError: If no MAC address is defined for the |
| requested NIC |
| """ |
| nic_name = self.params.objects("nics")[nic_index] |
| nic_params = self.params.object_params(nic_name) |
| mac = (nic_params.get("nic_mac") or |
| virt_utils.get_mac_address(self.instance, nic_index)) |
| if not mac: |
| raise VMMACAddressMissingError(nic_index) |
| return mac |
| |
| |
| def verify_kernel_crash(self): |
| """ |
| Find kernel crash message on the VM serial console. |
| |
| @raise: VMDeadKernelCrashError, in case a kernel crash message was |
| found. |
| """ |
| if self.serial_console is not None: |
| data = self.serial_console.get_output() |
| match = re.search(r"BUG:.*---\[ end trace .* \]---", data, |
| re.DOTALL|re.MULTILINE) |
| if match is not None: |
| raise VMDeadKernelCrashError(match.group(0)) |
| |
| |
| def get_params(self): |
| """ |
| Return the VM's params dict. Most modified params take effect only |
| upon VM.create(). |
| """ |
| return self.params |
| |
| |
| def get_serial_console_filename(self): |
| """ |
| Return the serial console filename. |
| """ |
| return "/tmp/serial-%s" % self.instance |
| |
| |
| def get_testlog_filename(self): |
| """ |
| Return the testlog filename. |
| """ |
| return "/tmp/testlog-%s" % self.instance |
| |
| |
| @error.context_aware |
| def login(self, nic_index=0, timeout=10): |
| """ |
| Log into the guest via SSH/Telnet/Netcat. |
| If timeout expires while waiting for output from the guest (e.g. a |
| password prompt or a shell prompt) -- fail. |
| |
| @param nic_index: The index of the NIC to connect to. |
| @param timeout: Time (seconds) before giving up logging into the |
| guest. |
| @return: A ShellSession object. |
| """ |
| error.context("logging into '%s'" % self.name) |
| username = self.params.get("username", "") |
| password = self.params.get("password", "") |
| prompt = self.params.get("shell_prompt", "[\#\$]") |
| linesep = eval("'%s'" % self.params.get("shell_linesep", r"\n")) |
| client = self.params.get("shell_client") |
| address = self.get_address(nic_index) |
| port = self.get_port(int(self.params.get("shell_port"))) |
| log_filename = ("session-%s-%s.log" % |
| (self.name, virt_utils.generate_random_string(4))) |
| session = virt_utils.remote_login(client, address, port, username, |
| password, prompt, linesep, |
| log_filename, timeout) |
| session.set_status_test_command(self.params.get("status_test_command", |
| "")) |
| return session |
| |
| |
| def remote_login(self, nic_index=0, timeout=10): |
| """ |
| Alias for login() for backward compatibility. |
| """ |
| return self.login(nic_index, timeout) |
| |
| |
| def wait_for_login(self, nic_index=0, timeout=240, internal_timeout=10): |
| """ |
| Make multiple attempts to log into the guest via SSH/Telnet/Netcat. |
| |
| @param nic_index: The index of the NIC to connect to. |
| @param timeout: Time (seconds) to keep trying to log in. |
| @param internal_timeout: Timeout to pass to login(). |
| @return: A ShellSession object. |
| """ |
| logging.debug("Attempting to log into '%s' (timeout %ds)", self.name, |
| timeout) |
| end_time = time.time() + timeout |
| while time.time() < end_time: |
| try: |
| return self.login(nic_index, internal_timeout) |
| except (virt_utils.LoginError, VMError), e: |
| logging.debug(e) |
| time.sleep(2) |
| # Timeout expired; try one more time but don't catch exceptions |
| return self.login(nic_index, internal_timeout) |
| |
| |
| @error.context_aware |
| def copy_files_to(self, host_path, guest_path, nic_index=0, verbose=False, |
| timeout=600): |
| """ |
| Transfer files to the remote host(guest). |
| |
| @param host_path: Host path |
| @param guest_path: Guest path |
| @param nic_index: The index of the NIC to connect to. |
| @param verbose: If True, log some stats using logging.debug (RSS only) |
| @param timeout: Time (seconds) before giving up on doing the remote |
| copy. |
| """ |
| error.context("sending file(s) to '%s'" % self.name) |
| username = self.params.get("username", "") |
| password = self.params.get("password", "") |
| client = self.params.get("file_transfer_client") |
| address = self.get_address(nic_index) |
| port = self.get_port(int(self.params.get("file_transfer_port"))) |
| log_filename = ("transfer-%s-to-%s-%s.log" % |
| (self.name, address, |
| virt_utils.generate_random_string(4))) |
| virt_utils.copy_files_to(address, client, username, password, port, |
| host_path, guest_path, log_filename, verbose, |
| timeout) |
| |
| |
| @error.context_aware |
| def copy_files_from(self, guest_path, host_path, nic_index=0, |
| verbose=False, timeout=600): |
| """ |
| Transfer files from the guest. |
| |
| @param host_path: Guest path |
| @param guest_path: Host path |
| @param nic_index: The index of the NIC to connect to. |
| @param verbose: If True, log some stats using logging.debug (RSS only) |
| @param timeout: Time (seconds) before giving up on doing the remote |
| copy. |
| """ |
| error.context("receiving file(s) from '%s'" % self.name) |
| username = self.params.get("username", "") |
| password = self.params.get("password", "") |
| client = self.params.get("file_transfer_client") |
| address = self.get_address(nic_index) |
| port = self.get_port(int(self.params.get("file_transfer_port"))) |
| log_filename = ("transfer-%s-from-%s-%s.log" % |
| (self.name, address, |
| virt_utils.generate_random_string(4))) |
| virt_utils.copy_files_from(address, client, username, password, port, |
| guest_path, host_path, log_filename, |
| verbose, timeout) |
| |
| |
| @error.context_aware |
| def serial_login(self, timeout=10): |
| """ |
| Log into the guest via the serial console. |
| If timeout expires while waiting for output from the guest (e.g. a |
| password prompt or a shell prompt) -- fail. |
| |
| @param timeout: Time (seconds) before giving up logging into the guest. |
| @return: ShellSession object on success and None on failure. |
| """ |
| error.context("logging into '%s' via serial console" % self.name) |
| username = self.params.get("username", "") |
| password = self.params.get("password", "") |
| prompt = self.params.get("shell_prompt", "[\#\$]") |
| linesep = eval("'%s'" % self.params.get("shell_linesep", r"\n")) |
| status_test_command = self.params.get("status_test_command", "") |
| |
| self.serial_console.set_linesep(linesep) |
| self.serial_console.set_status_test_command(status_test_command) |
| |
| # Try to get a login prompt |
| self.serial_console.sendline() |
| |
| virt_utils._remote_login(self.serial_console, username, password, |
| prompt, timeout) |
| return self.serial_console |
| |
| |
| def wait_for_serial_login(self, timeout=240, internal_timeout=10): |
| """ |
| Make multiple attempts to log into the guest via serial console. |
| |
| @param timeout: Time (seconds) to keep trying to log in. |
| @param internal_timeout: Timeout to pass to serial_login(). |
| @return: A ShellSession object. |
| """ |
| logging.debug("Attempting to log into '%s' via serial console " |
| "(timeout %ds)", self.name, timeout) |
| end_time = time.time() + timeout |
| while time.time() < end_time: |
| try: |
| return self.serial_login(internal_timeout) |
| except virt_utils.LoginError, e: |
| logging.debug(e) |
| time.sleep(2) |
| # Timeout expired; try one more time but don't catch exceptions |
| return self.serial_login(internal_timeout) |
| |
| |
| def get_uuid(self): |
| """ |
| Catch UUID of the VM. |
| |
| @return: None,if not specified in config file |
| """ |
| if self.params.get("uuid") == "random": |
| return self.uuid |
| else: |
| return self.params.get("uuid", None) |
| |
| |
| def send_string(self, str): |
| """ |
| Send a string to the VM. |
| |
| @param str: String, that must consist of alphanumeric characters only. |
| Capital letters are allowed. |
| """ |
| for char in str: |
| if char.isupper(): |
| self.send_key("shift-%s" % char.lower()) |
| else: |
| self.send_key(char) |
| |
| |
| def get_cpu_count(self): |
| """ |
| Get the cpu count of the VM. |
| """ |
| session = self.login() |
| try: |
| return int(session.cmd(self.params.get("cpu_chk_cmd"))) |
| finally: |
| session.close() |
| |
| |
| def get_memory_size(self, cmd=None): |
| """ |
| Get bootup memory size of the VM. |
| |
| @param check_cmd: Command used to check memory. If not provided, |
| self.params.get("mem_chk_cmd") will be used. |
| """ |
| session = self.login() |
| try: |
| if not cmd: |
| cmd = self.params.get("mem_chk_cmd") |
| mem_str = session.cmd(cmd) |
| mem = re.findall("([0-9]+)", mem_str) |
| mem_size = 0 |
| for m in mem: |
| mem_size += int(m) |
| if "GB" in mem_str: |
| mem_size *= 1024 |
| elif "MB" in mem_str: |
| pass |
| else: |
| mem_size /= 1024 |
| return int(mem_size) |
| finally: |
| session.close() |
| |
| |
| def get_current_memory_size(self): |
| """ |
| Get current memory size of the VM, rather than bootup memory. |
| """ |
| cmd = self.params.get("mem_chk_cur_cmd") |
| return self.get_memory_size(cmd) |
| |
| |
| # |
| # Public API - *must* be reimplemented with virt specific code |
| # |
| def is_alive(self): |
| """ |
| Return True if the VM is alive and the management interface is responsive. |
| """ |
| raise NotImplementedError |
| |
| |
| def is_dead(self): |
| """ |
| Return True if the the VM is dead. |
| """ |
| raise NotImplementedError |
| |
| |
| def get_address(self, index=0): |
| """ |
| Return the IP address of a NIC of the guest |
| |
| @param index: Index of the NIC whose address is requested. |
| @raise VMMACAddressMissingError: If no MAC address is defined for the |
| requested NIC |
| @raise VMIPAddressMissingError: If no IP address is found for the the |
| NIC's MAC address |
| @raise VMAddressVerificationError: If the MAC-IP address mapping cannot |
| be verified (using arping) |
| """ |
| raise NotImplementedError |
| |
| |
| def clone(self, name, **params): |
| """ |
| Return a clone of the VM object with optionally modified parameters. |
| |
| This method should be implemented by |
| """ |
| raise NotImplementedError |
| |
| |
| def destroy(self, gracefully=True, free_mac_addresses=True): |
| """ |
| Destroy the VM. |
| |
| If gracefully is True, first attempt to shutdown the VM with a shell |
| command. Then, attempt to destroy the VM via the monitor with a 'quit' |
| command. If that fails, send SIGKILL to the qemu process. |
| |
| @param gracefully: If True, an attempt will be made to end the VM |
| using a shell command before trying to end the qemu process |
| with a 'quit' or a kill signal. |
| @param free_mac_addresses: If True, the MAC addresses used by the VM |
| will be freed. |
| """ |
| raise NotImplementedError |
| |
| |
| def migrate(self, timeout=3600, protocol="tcp", cancel_delay=None, |
| offline=False, stable_check=False, clean=True, |
| save_path="/tmp", dest_host="localhost", remote_port=None): |
| """ |
| Migrate the VM. |
| |
| If the migration is local, the VM object's state is switched with that |
| of the destination VM. Otherwise, the state is switched with that of |
| a dead VM (returned by self.clone()). |
| |
| @param timeout: Time to wait for migration to complete. |
| @param protocol: Migration protocol ('tcp', 'unix' or 'exec'). |
| @param cancel_delay: If provided, specifies a time duration after which |
| migration will be canceled. Used for testing migrate_cancel. |
| @param offline: If True, pause the source VM before migration. |
| @param stable_check: If True, compare the VM's state after migration to |
| its state before migration and raise an exception if they |
| differ. |
| @param clean: If True, delete the saved state files (relevant only if |
| stable_check is also True). |
| @save_path: The path for state files. |
| @param dest_host: Destination host (defaults to 'localhost'). |
| @param remote_port: Port to use for remote migration. |
| """ |
| raise NotImplementedError |
| |
| |
| def reboot(self, session=None, method="shell", nic_index=0, timeout=240): |
| """ |
| Reboot the VM and wait for it to come back up by trying to log in until |
| timeout expires. |
| |
| @param session: A shell session object or None. |
| @param method: Reboot method. Can be "shell" (send a shell reboot |
| command) or "system_reset" (send a system_reset monitor command). |
| @param nic_index: Index of NIC to access in the VM, when logging in |
| after rebooting. |
| @param timeout: Time to wait for login to succeed (after rebooting). |
| @return: A new shell session object. |
| """ |
| raise NotImplementedError |
| |
| |
| # should this really be expected from VMs of all hypervisor types? |
| def send_key(self, keystr): |
| """ |
| Send a key event to the VM. |
| |
| @param: keystr: A key event string (e.g. "ctrl-alt-delete") |
| """ |
| raise NotImplementedError |
| |
| |
| def save_to_file(self, path): |
| """ |
| Save the state of virtual machine to a file through migrate to |
| exec |
| """ |
| raise NotImplementedError |
| |
| |
| def needs_restart(self, name, params, basedir): |
| """ |
| Based on virt preprocessing information, decide whether the VM needs |
| a restart. |
| """ |
| raise NotImplementedError |