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