1import os, logging, time, glob, re 2from autotest_lib.client.common_lib import error 3from autotest_lib.client.bin import utils 4import virt_utils 5 6class VMError(Exception): 7 pass 8 9 10class VMCreateError(VMError): 11 def __init__(self, cmd, status, output): 12 VMError.__init__(self, cmd, status, output) 13 self.cmd = cmd 14 self.status = status 15 self.output = output 16 17 def __str__(self): 18 return ("VM creation command failed: %r (status: %s, " 19 "output: %r)" % (self.cmd, self.status, self.output)) 20 21 22class VMHashMismatchError(VMError): 23 def __init__(self, actual, expected): 24 VMError.__init__(self, actual, expected) 25 self.actual_hash = actual 26 self.expected_hash = expected 27 28 def __str__(self): 29 return ("CD image hash (%s) differs from expected one (%s)" % 30 (self.actual_hash, self.expected_hash)) 31 32 33class VMImageMissingError(VMError): 34 def __init__(self, filename): 35 VMError.__init__(self, filename) 36 self.filename = filename 37 38 def __str__(self): 39 return "CD image file not found: %r" % self.filename 40 41 42class VMImageCheckError(VMError): 43 def __init__(self, filename): 44 VMError.__init__(self, filename) 45 self.filename = filename 46 47 def __str__(self): 48 return "Errors found on image: %r" % self.filename 49 50 51class VMBadPATypeError(VMError): 52 def __init__(self, pa_type): 53 VMError.__init__(self, pa_type) 54 self.pa_type = pa_type 55 56 def __str__(self): 57 return "Unsupported PCI assignable type: %r" % self.pa_type 58 59 60class VMPAError(VMError): 61 def __init__(self, pa_type): 62 VMError.__init__(self, pa_type) 63 self.pa_type = pa_type 64 65 def __str__(self): 66 return ("No PCI assignable devices could be assigned " 67 "(pci_assignable=%r)" % self.pa_type) 68 69 70class VMPostCreateError(VMError): 71 def __init__(self, cmd, output): 72 VMError.__init__(self, cmd, output) 73 self.cmd = cmd 74 self.output = output 75 76 77class VMHugePageError(VMPostCreateError): 78 def __str__(self): 79 return ("Cannot allocate hugepage memory (command: %r, " 80 "output: %r)" % (self.cmd, self.output)) 81 82 83class VMKVMInitError(VMPostCreateError): 84 def __str__(self): 85 return ("Cannot initialize KVM (command: %r, output: %r)" % 86 (self.cmd, self.output)) 87 88 89class VMDeadError(VMError): 90 def __init__(self, reason='', detail=''): 91 VMError.__init__(self) 92 self.reason = reason 93 self.detail = detail 94 95 def __str__(self): 96 msg = "VM is dead" 97 if self.reason: 98 msg += " reason: %s" % self.reason 99 if self.detail: 100 msg += " detail: %r" % self.detail 101 return (msg) 102 103 104class VMDeadKernelCrashError(VMError): 105 def __init__(self, kernel_crash): 106 VMError.__init__(self, kernel_crash) 107 self.kernel_crash = kernel_crash 108 109 def __str__(self): 110 return ("VM is dead due to a kernel crash:\n%s" % self.kernel_crash) 111 112 113class VMAddressError(VMError): 114 pass 115 116 117class VMPortNotRedirectedError(VMAddressError): 118 def __init__(self, port): 119 VMAddressError.__init__(self, port) 120 self.port = port 121 122 def __str__(self): 123 return "Port not redirected: %s" % self.port 124 125 126class VMAddressVerificationError(VMAddressError): 127 def __init__(self, mac, ip): 128 VMAddressError.__init__(self, mac, ip) 129 self.mac = mac 130 self.ip = ip 131 132 def __str__(self): 133 return ("Cannot verify MAC-IP address mapping using arping: " 134 "%s ---> %s" % (self.mac, self.ip)) 135 136 137class VMMACAddressMissingError(VMAddressError): 138 def __init__(self, nic_index): 139 VMAddressError.__init__(self, nic_index) 140 self.nic_index = nic_index 141 142 def __str__(self): 143 return "No MAC address defined for NIC #%s" % self.nic_index 144 145 146class VMIPAddressMissingError(VMAddressError): 147 def __init__(self, mac): 148 VMAddressError.__init__(self, mac) 149 self.mac = mac 150 151 def __str__(self): 152 return "Cannot find IP address for MAC address %s" % self.mac 153 154 155class VMMigrateError(VMError): 156 pass 157 158 159class VMMigrateTimeoutError(VMMigrateError): 160 pass 161 162 163class VMMigrateCancelError(VMMigrateError): 164 pass 165 166 167class VMMigrateFailedError(VMMigrateError): 168 pass 169 170class VMMigrateProtoUnsupportedError(VMMigrateError): 171 pass 172 173 174class VMMigrateStateMismatchError(VMMigrateError): 175 def __init__(self, src_hash, dst_hash): 176 VMMigrateError.__init__(self, src_hash, dst_hash) 177 self.src_hash = src_hash 178 self.dst_hash = dst_hash 179 180 def __str__(self): 181 return ("Mismatch of VM state before and after migration (%s != %s)" % 182 (self.src_hash, self.dst_hash)) 183 184 185class VMRebootError(VMError): 186 pass 187 188class VMStatusError(VMError): 189 pass 190 191def get_image_filename(params, root_dir): 192 """ 193 Generate an image path from params and root_dir. 194 195 @param params: Dictionary containing the test parameters. 196 @param root_dir: Base directory for relative filenames. 197 198 @note: params should contain: 199 image_name -- the name of the image file, without extension 200 image_format -- the format of the image (qcow2, raw etc) 201 """ 202 image_name = params.get("image_name", "image") 203 image_format = params.get("image_format", "qcow2") 204 if params.get("image_raw_device") == "yes": 205 return image_name 206 image_filename = "%s.%s" % (image_name, image_format) 207 image_filename = virt_utils.get_path(root_dir, image_filename) 208 return image_filename 209 210 211def create_image(params, root_dir): 212 """ 213 Create an image using qemu_image. 214 215 @param params: Dictionary containing the test parameters. 216 @param root_dir: Base directory for relative filenames. 217 218 @note: params should contain: 219 image_name -- the name of the image file, without extension 220 image_format -- the format of the image (qcow2, raw etc) 221 image_cluster_size (optional) -- the cluster size for the image 222 image_size -- the requested size of the image (a string 223 qemu-img can understand, such as '10G') 224 """ 225 qemu_img_cmd = virt_utils.get_path(root_dir, params.get("qemu_img_binary", 226 "qemu-img")) 227 qemu_img_cmd += " create" 228 229 format = params.get("image_format", "qcow2") 230 qemu_img_cmd += " -f %s" % format 231 232 image_cluster_size = params.get("image_cluster_size", None) 233 if image_cluster_size is not None: 234 qemu_img_cmd += " -o cluster_size=%s" % image_cluster_size 235 236 image_filename = get_image_filename(params, root_dir) 237 qemu_img_cmd += " %s" % image_filename 238 239 size = params.get("image_size", "10G") 240 qemu_img_cmd += " %s" % size 241 242 utils.system(qemu_img_cmd) 243 return image_filename 244 245 246def remove_image(params, root_dir): 247 """ 248 Remove an image file. 249 250 @param params: A dict 251 @param root_dir: Base directory for relative filenames. 252 253 @note: params should contain: 254 image_name -- the name of the image file, without extension 255 image_format -- the format of the image (qcow2, raw etc) 256 """ 257 image_filename = get_image_filename(params, root_dir) 258 logging.debug("Removing image file %s", image_filename) 259 if os.path.exists(image_filename): 260 os.unlink(image_filename) 261 else: 262 logging.debug("Image file %s not found") 263 264 265def check_image(params, root_dir): 266 """ 267 Check an image using the appropriate tools for each virt backend. 268 269 @param params: Dictionary containing the test parameters. 270 @param root_dir: Base directory for relative filenames. 271 272 @note: params should contain: 273 image_name -- the name of the image file, without extension 274 image_format -- the format of the image (qcow2, raw etc) 275 276 @raise VMImageCheckError: In case qemu-img check fails on the image. 277 """ 278 vm_type = params.get("vm_type") 279 if vm_type == 'kvm': 280 image_filename = get_image_filename(params, root_dir) 281 logging.debug("Checking image file %s", image_filename) 282 qemu_img_cmd = virt_utils.get_path(root_dir, 283 params.get("qemu_img_binary", "qemu-img")) 284 image_is_qcow2 = params.get("image_format") == 'qcow2' 285 if os.path.exists(image_filename) and image_is_qcow2: 286 # Verifying if qemu-img supports 'check' 287 q_result = utils.run(qemu_img_cmd, ignore_status=True) 288 q_output = q_result.stdout 289 check_img = True 290 if not "check" in q_output: 291 logging.error("qemu-img does not support 'check', " 292 "skipping check") 293 check_img = False 294 if not "info" in q_output: 295 logging.error("qemu-img does not support 'info', " 296 "skipping check") 297 check_img = False 298 if check_img: 299 try: 300 utils.system("%s info %s" % (qemu_img_cmd, image_filename)) 301 except error.CmdError: 302 logging.error("Error getting info from image %s", 303 image_filename) 304 305 cmd_result = utils.run("%s check %s" % 306 (qemu_img_cmd, image_filename), 307 ignore_status=True) 308 # Error check, large chances of a non-fatal problem. 309 # There are chances that bad data was skipped though 310 if cmd_result.exit_status == 1: 311 for e_line in cmd_result.stdout.splitlines(): 312 logging.error("[stdout] %s", e_line) 313 for e_line in cmd_result.stderr.splitlines(): 314 logging.error("[stderr] %s", e_line) 315 raise error.TestWarn("qemu-img check error. Some bad data " 316 "in the image may have gone unnoticed") 317 # Exit status 2 is data corruption for sure, so fail the test 318 elif cmd_result.exit_status == 2: 319 for e_line in cmd_result.stdout.splitlines(): 320 logging.error("[stdout] %s", e_line) 321 for e_line in cmd_result.stderr.splitlines(): 322 logging.error("[stderr] %s", e_line) 323 raise VMImageCheckError(image_filename) 324 # Leaked clusters, they are known to be harmless to data 325 # integrity 326 elif cmd_result.exit_status == 3: 327 raise error.TestWarn("Leaked clusters were noticed during " 328 "image check. No data integrity " 329 "problem was found though.") 330 331 else: 332 if not os.path.exists(image_filename): 333 logging.debug("Image file %s not found, skipping check", 334 image_filename) 335 elif not image_is_qcow2: 336 logging.debug("Image file %s not qcow2, skipping check", 337 image_filename) 338 339 340class BaseVM(object): 341 """ 342 Base class for all hypervisor specific VM subclasses. 343 344 This class should not be used directly, that is, do not attempt to 345 instantiate and use this class. Instead, one should implement a subclass 346 that implements, at the very least, all methods defined right after the 347 the comment blocks that are marked with: 348 349 "Public API - *must* be reimplemented with virt specific code" 350 351 and 352 353 "Protected API - *must* be reimplemented with virt specific classes" 354 355 The current proposal regarding methods naming convention is: 356 357 - Public API methods: named in the usual way, consumed by tests 358 - Protected API methods: name begins with a single underline, to be 359 consumed only by BaseVM and subclasses 360 - Private API methods: name begins with double underline, to be consumed 361 only by the VM subclass itself (usually implements virt specific 362 functionality: example: __make_qemu_command()) 363 364 So called "protected" methods are intended to be used only by VM classes, 365 and not be consumed by tests. Theses should respect a naming convention 366 and always be preceeded by a single underline. 367 368 Currently most (if not all) methods are public and appears to be consumed 369 by tests. It is a ongoing task to determine whether methods should be 370 "public" or "protected". 371 """ 372 373 # 374 # Assuming that all low-level hypervisor have at least migration via tcp 375 # (true for xen & kvm). Also true for libvirt (using xen and kvm drivers) 376 # 377 MIGRATION_PROTOS = ['tcp', ] 378 379 def __init__(self, name, params): 380 self.name = name 381 self.params = params 382 383 # 384 # Assuming all low-level hypervisors will have a serial (like) console 385 # connection to the guest. libvirt also supports serial (like) consoles 386 # (virDomainOpenConsole). subclasses should set this to an object that 387 # is or behaves like aexpect.ShellSession. 388 # 389 self.serial_console = None 390 391 self._generate_unique_id() 392 393 394 def _generate_unique_id(self): 395 """ 396 Generate a unique identifier for this VM 397 """ 398 while True: 399 self.instance = (time.strftime("%Y%m%d-%H%M%S-") + 400 virt_utils.generate_random_string(4)) 401 if not glob.glob("/tmp/*%s" % self.instance): 402 break 403 404 405 # 406 # Public API - could be reimplemented with virt specific code 407 # 408 def verify_alive(self): 409 """ 410 Make sure the VM is alive and that the main monitor is responsive. 411 412 Can be subclassed to provide better information on why the VM is 413 not alive (reason, detail) 414 415 @raise VMDeadError: If the VM is dead 416 @raise: Various monitor exceptions if the monitor is unresponsive 417 """ 418 if self.is_dead(): 419 raise VMDeadError 420 421 422 def get_mac_address(self, nic_index=0): 423 """ 424 Return the MAC address of a NIC. 425 426 @param nic_index: Index of the NIC 427 @raise VMMACAddressMissingError: If no MAC address is defined for the 428 requested NIC 429 """ 430 nic_name = self.params.objects("nics")[nic_index] 431 nic_params = self.params.object_params(nic_name) 432 mac = (nic_params.get("nic_mac") or 433 virt_utils.get_mac_address(self.instance, nic_index)) 434 if not mac: 435 raise VMMACAddressMissingError(nic_index) 436 return mac 437 438 439 def verify_kernel_crash(self): 440 """ 441 Find kernel crash message on the VM serial console. 442 443 @raise: VMDeadKernelCrashError, in case a kernel crash message was 444 found. 445 """ 446 if self.serial_console is not None: 447 data = self.serial_console.get_output() 448 match = re.search(r"BUG:.*---\[ end trace .* \]---", data, 449 re.DOTALL|re.MULTILINE) 450 if match is not None: 451 raise VMDeadKernelCrashError(match.group(0)) 452 453 454 def get_params(self): 455 """ 456 Return the VM's params dict. Most modified params take effect only 457 upon VM.create(). 458 """ 459 return self.params 460 461 462 def get_serial_console_filename(self): 463 """ 464 Return the serial console filename. 465 """ 466 return "/tmp/serial-%s" % self.instance 467 468 469 def get_testlog_filename(self): 470 """ 471 Return the testlog filename. 472 """ 473 return "/tmp/testlog-%s" % self.instance 474 475 476 @error.context_aware 477 def login(self, nic_index=0, timeout=10): 478 """ 479 Log into the guest via SSH/Telnet/Netcat. 480 If timeout expires while waiting for output from the guest (e.g. a 481 password prompt or a shell prompt) -- fail. 482 483 @param nic_index: The index of the NIC to connect to. 484 @param timeout: Time (seconds) before giving up logging into the 485 guest. 486 @return: A ShellSession object. 487 """ 488 error.context("logging into '%s'" % self.name) 489 username = self.params.get("username", "") 490 password = self.params.get("password", "") 491 prompt = self.params.get("shell_prompt", "[\#\$]") 492 linesep = eval("'%s'" % self.params.get("shell_linesep", r"\n")) 493 client = self.params.get("shell_client") 494 address = self.get_address(nic_index) 495 port = self.get_port(int(self.params.get("shell_port"))) 496 log_filename = ("session-%s-%s.log" % 497 (self.name, virt_utils.generate_random_string(4))) 498 session = virt_utils.remote_login(client, address, port, username, 499 password, prompt, linesep, 500 log_filename, timeout) 501 session.set_status_test_command(self.params.get("status_test_command", 502 "")) 503 return session 504 505 506 def remote_login(self, nic_index=0, timeout=10): 507 """ 508 Alias for login() for backward compatibility. 509 """ 510 return self.login(nic_index, timeout) 511 512 513 def wait_for_login(self, nic_index=0, timeout=240, internal_timeout=10): 514 """ 515 Make multiple attempts to log into the guest via SSH/Telnet/Netcat. 516 517 @param nic_index: The index of the NIC to connect to. 518 @param timeout: Time (seconds) to keep trying to log in. 519 @param internal_timeout: Timeout to pass to login(). 520 @return: A ShellSession object. 521 """ 522 logging.debug("Attempting to log into '%s' (timeout %ds)", self.name, 523 timeout) 524 end_time = time.time() + timeout 525 while time.time() < end_time: 526 try: 527 return self.login(nic_index, internal_timeout) 528 except (virt_utils.LoginError, VMError), e: 529 logging.debug(e) 530 time.sleep(2) 531 # Timeout expired; try one more time but don't catch exceptions 532 return self.login(nic_index, internal_timeout) 533 534 535 @error.context_aware 536 def copy_files_to(self, host_path, guest_path, nic_index=0, verbose=False, 537 timeout=600): 538 """ 539 Transfer files to the remote host(guest). 540 541 @param host_path: Host path 542 @param guest_path: Guest path 543 @param nic_index: The index of the NIC to connect to. 544 @param verbose: If True, log some stats using logging.debug (RSS only) 545 @param timeout: Time (seconds) before giving up on doing the remote 546 copy. 547 """ 548 error.context("sending file(s) to '%s'" % self.name) 549 username = self.params.get("username", "") 550 password = self.params.get("password", "") 551 client = self.params.get("file_transfer_client") 552 address = self.get_address(nic_index) 553 port = self.get_port(int(self.params.get("file_transfer_port"))) 554 log_filename = ("transfer-%s-to-%s-%s.log" % 555 (self.name, address, 556 virt_utils.generate_random_string(4))) 557 virt_utils.copy_files_to(address, client, username, password, port, 558 host_path, guest_path, log_filename, verbose, 559 timeout) 560 561 562 @error.context_aware 563 def copy_files_from(self, guest_path, host_path, nic_index=0, 564 verbose=False, timeout=600): 565 """ 566 Transfer files from the guest. 567 568 @param host_path: Guest path 569 @param guest_path: Host path 570 @param nic_index: The index of the NIC to connect to. 571 @param verbose: If True, log some stats using logging.debug (RSS only) 572 @param timeout: Time (seconds) before giving up on doing the remote 573 copy. 574 """ 575 error.context("receiving file(s) from '%s'" % self.name) 576 username = self.params.get("username", "") 577 password = self.params.get("password", "") 578 client = self.params.get("file_transfer_client") 579 address = self.get_address(nic_index) 580 port = self.get_port(int(self.params.get("file_transfer_port"))) 581 log_filename = ("transfer-%s-from-%s-%s.log" % 582 (self.name, address, 583 virt_utils.generate_random_string(4))) 584 virt_utils.copy_files_from(address, client, username, password, port, 585 guest_path, host_path, log_filename, 586 verbose, timeout) 587 588 589 @error.context_aware 590 def serial_login(self, timeout=10): 591 """ 592 Log into the guest via the serial console. 593 If timeout expires while waiting for output from the guest (e.g. a 594 password prompt or a shell prompt) -- fail. 595 596 @param timeout: Time (seconds) before giving up logging into the guest. 597 @return: ShellSession object on success and None on failure. 598 """ 599 error.context("logging into '%s' via serial console" % self.name) 600 username = self.params.get("username", "") 601 password = self.params.get("password", "") 602 prompt = self.params.get("shell_prompt", "[\#\$]") 603 linesep = eval("'%s'" % self.params.get("shell_linesep", r"\n")) 604 status_test_command = self.params.get("status_test_command", "") 605 606 self.serial_console.set_linesep(linesep) 607 self.serial_console.set_status_test_command(status_test_command) 608 609 # Try to get a login prompt 610 self.serial_console.sendline() 611 612 virt_utils._remote_login(self.serial_console, username, password, 613 prompt, timeout) 614 return self.serial_console 615 616 617 def wait_for_serial_login(self, timeout=240, internal_timeout=10): 618 """ 619 Make multiple attempts to log into the guest via serial console. 620 621 @param timeout: Time (seconds) to keep trying to log in. 622 @param internal_timeout: Timeout to pass to serial_login(). 623 @return: A ShellSession object. 624 """ 625 logging.debug("Attempting to log into '%s' via serial console " 626 "(timeout %ds)", self.name, timeout) 627 end_time = time.time() + timeout 628 while time.time() < end_time: 629 try: 630 return self.serial_login(internal_timeout) 631 except virt_utils.LoginError, e: 632 logging.debug(e) 633 time.sleep(2) 634 # Timeout expired; try one more time but don't catch exceptions 635 return self.serial_login(internal_timeout) 636 637 638 def get_uuid(self): 639 """ 640 Catch UUID of the VM. 641 642 @return: None,if not specified in config file 643 """ 644 if self.params.get("uuid") == "random": 645 return self.uuid 646 else: 647 return self.params.get("uuid", None) 648 649 650 def send_string(self, str): 651 """ 652 Send a string to the VM. 653 654 @param str: String, that must consist of alphanumeric characters only. 655 Capital letters are allowed. 656 """ 657 for char in str: 658 if char.isupper(): 659 self.send_key("shift-%s" % char.lower()) 660 else: 661 self.send_key(char) 662 663 664 def get_cpu_count(self): 665 """ 666 Get the cpu count of the VM. 667 """ 668 session = self.login() 669 try: 670 return int(session.cmd(self.params.get("cpu_chk_cmd"))) 671 finally: 672 session.close() 673 674 675 def get_memory_size(self, cmd=None): 676 """ 677 Get bootup memory size of the VM. 678 679 @param check_cmd: Command used to check memory. If not provided, 680 self.params.get("mem_chk_cmd") will be used. 681 """ 682 session = self.login() 683 try: 684 if not cmd: 685 cmd = self.params.get("mem_chk_cmd") 686 mem_str = session.cmd(cmd) 687 mem = re.findall("([0-9]+)", mem_str) 688 mem_size = 0 689 for m in mem: 690 mem_size += int(m) 691 if "GB" in mem_str: 692 mem_size *= 1024 693 elif "MB" in mem_str: 694 pass 695 else: 696 mem_size /= 1024 697 return int(mem_size) 698 finally: 699 session.close() 700 701 702 def get_current_memory_size(self): 703 """ 704 Get current memory size of the VM, rather than bootup memory. 705 """ 706 cmd = self.params.get("mem_chk_cur_cmd") 707 return self.get_memory_size(cmd) 708 709 710 # 711 # Public API - *must* be reimplemented with virt specific code 712 # 713 def is_alive(self): 714 """ 715 Return True if the VM is alive and the management interface is responsive. 716 """ 717 raise NotImplementedError 718 719 720 def is_dead(self): 721 """ 722 Return True if the the VM is dead. 723 """ 724 raise NotImplementedError 725 726 727 def get_address(self, index=0): 728 """ 729 Return the IP address of a NIC of the guest 730 731 @param index: Index of the NIC whose address is requested. 732 @raise VMMACAddressMissingError: If no MAC address is defined for the 733 requested NIC 734 @raise VMIPAddressMissingError: If no IP address is found for the the 735 NIC's MAC address 736 @raise VMAddressVerificationError: If the MAC-IP address mapping cannot 737 be verified (using arping) 738 """ 739 raise NotImplementedError 740 741 742 def clone(self, name, **params): 743 """ 744 Return a clone of the VM object with optionally modified parameters. 745 746 This method should be implemented by 747 """ 748 raise NotImplementedError 749 750 751 def destroy(self, gracefully=True, free_mac_addresses=True): 752 """ 753 Destroy the VM. 754 755 If gracefully is True, first attempt to shutdown the VM with a shell 756 command. Then, attempt to destroy the VM via the monitor with a 'quit' 757 command. If that fails, send SIGKILL to the qemu process. 758 759 @param gracefully: If True, an attempt will be made to end the VM 760 using a shell command before trying to end the qemu process 761 with a 'quit' or a kill signal. 762 @param free_mac_addresses: If True, the MAC addresses used by the VM 763 will be freed. 764 """ 765 raise NotImplementedError 766 767 768 def migrate(self, timeout=3600, protocol="tcp", cancel_delay=None, 769 offline=False, stable_check=False, clean=True, 770 save_path="/tmp", dest_host="localhost", remote_port=None): 771 """ 772 Migrate the VM. 773 774 If the migration is local, the VM object's state is switched with that 775 of the destination VM. Otherwise, the state is switched with that of 776 a dead VM (returned by self.clone()). 777 778 @param timeout: Time to wait for migration to complete. 779 @param protocol: Migration protocol ('tcp', 'unix' or 'exec'). 780 @param cancel_delay: If provided, specifies a time duration after which 781 migration will be canceled. Used for testing migrate_cancel. 782 @param offline: If True, pause the source VM before migration. 783 @param stable_check: If True, compare the VM's state after migration to 784 its state before migration and raise an exception if they 785 differ. 786 @param clean: If True, delete the saved state files (relevant only if 787 stable_check is also True). 788 @save_path: The path for state files. 789 @param dest_host: Destination host (defaults to 'localhost'). 790 @param remote_port: Port to use for remote migration. 791 """ 792 raise NotImplementedError 793 794 795 def reboot(self, session=None, method="shell", nic_index=0, timeout=240): 796 """ 797 Reboot the VM and wait for it to come back up by trying to log in until 798 timeout expires. 799 800 @param session: A shell session object or None. 801 @param method: Reboot method. Can be "shell" (send a shell reboot 802 command) or "system_reset" (send a system_reset monitor command). 803 @param nic_index: Index of NIC to access in the VM, when logging in 804 after rebooting. 805 @param timeout: Time to wait for login to succeed (after rebooting). 806 @return: A new shell session object. 807 """ 808 raise NotImplementedError 809 810 811 # should this really be expected from VMs of all hypervisor types? 812 def send_key(self, keystr): 813 """ 814 Send a key event to the VM. 815 816 @param: keystr: A key event string (e.g. "ctrl-alt-delete") 817 """ 818 raise NotImplementedError 819 820 821 def save_to_file(self, path): 822 """ 823 Save the state of virtual machine to a file through migrate to 824 exec 825 """ 826 raise NotImplementedError 827 828 829 def needs_restart(self, name, params, basedir): 830 """ 831 Based on virt preprocessing information, decide whether the VM needs 832 a restart. 833 """ 834 raise NotImplementedError 835