• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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