blob: eb9717b334b7da6bdadadc019489bf1584e2cf24 [file] [log] [blame]
lmr6f669ce2009-05-31 19:02:42 +00001#!/usr/bin/python
2import time, socket, os, logging
3import kvm_utils
4
5"""
6Utility classes and functions to handle Virtual Machine creation using qemu.
7
8@copyright: 2008-2009 Red Hat Inc.
9"""
10
11
12def get_image_filename(params, image_dir):
13 """
14 Generate an image path from params and image_dir.
15
16 @param params: Dictionary containing the test parameters.
17 @param image_dir: The directory where the image is to be located
18
19 @note: params should contain:
20 image_name -- the name of the image file, without extension
21 image_format -- the format of the image (qcow2, raw etc)
22 """
23 image_name = params.get("image_name", "image")
24 image_format = params.get("image_format", "qcow2")
25 image_filename = "%s.%s" % (image_name, image_format)
26 image_filename = os.path.join(image_dir, image_filename)
27 return image_filename
28
29
30def create_image(params, qemu_img_path, image_dir):
31 """
32 Create an image using qemu_image.
33
34 @param params: Dictionary containing the test parameters.
35 @param qemu_img_path: The path of the qemu-img binary
36 @param image_dir: The directory where the image is to be located
37
38 @note: params should contain:
39 image_name -- the name of the image file, without extension
40 image_format -- the format of the image (qcow2, raw etc)
41 image_size -- the requested size of the image (a string
42 qemu-img can understand, such as '10G')
43 """
44 qemu_img_cmd = qemu_img_path
45 qemu_img_cmd += " create"
46
47 format = params.get("image_format", "qcow2")
48 qemu_img_cmd += " -f %s" % format
49
50 image_filename = get_image_filename(params, image_dir)
51 qemu_img_cmd += " %s" % image_filename
52
53 size = params.get("image_size", "10G")
54 qemu_img_cmd += " %s" % size
55
56 logging.debug("Running qemu-img command:\n%s" % qemu_img_cmd)
57 (status, pid, output) = kvm_utils.run_bg(qemu_img_cmd, None,
58 logging.debug, "(qemu-img) ",
59 timeout=30)
60
61 if status:
62 logging.debug("qemu-img exited with status %d" % status)
63 logging.error("Could not create image %s" % image_filename)
64 return None
65 if not os.path.exists(image_filename):
66 logging.debug("Image file does not exist for some reason")
67 logging.error("Could not create image %s" % image_filename)
68 return None
69
70 logging.info("Image created in %s" % image_filename)
71 return image_filename
72
73
74def remove_image(params, image_dir):
75 """
76 Remove an image file.
77
78 @param params: A dict
79 @param image_dir: The directory where the image is to be located
80
81 @note: params should contain:
82 image_name -- the name of the image file, without extension
83 image_format -- the format of the image (qcow2, raw etc)
84 """
85 image_filename = get_image_filename(params, image_dir)
86 logging.debug("Removing image file %s..." % image_filename)
87 if os.path.exists(image_filename):
88 os.unlink(image_filename)
89 else:
90 logging.debug("Image file %s not found")
91
92
93class VM:
94 """
95 This class handles all basic VM operations.
96 """
97
98 def __init__(self, name, params, qemu_path, image_dir, iso_dir):
99 """
100 Initialize the object and set a few attributes.
101
102 @param name: The name of the object
103 @param params: A dict containing VM params
104 (see method make_qemu_command for a full description)
105 @param qemu_path: The path of the qemu binary
106 @param image_dir: The directory where images reside
107 @param iso_dir: The directory where ISOs reside
108 """
109 self.pid = None
110
111 self.name = name
112 self.params = params
113 self.qemu_path = qemu_path
114 self.image_dir = image_dir
115 self.iso_dir = iso_dir
116
117
lmr8b134f92009-06-08 14:47:31 +0000118 # Find available monitor filename
119 while True:
120 # The monitor filename should be unique
121 self.instance = time.strftime("%Y%m%d-%H%M%S-") + \
122 kvm_utils.generate_random_string(4)
123 self.monitor_file_name = os.path.join("/tmp",
124 "monitor-" + self.instance)
125 if not os.path.exists(self.monitor_file_name):
126 break
127
128
lmr2c241172009-06-08 15:11:29 +0000129 def clone(self, name=None, params=None, qemu_path=None, image_dir=None,
130 iso_dir=None):
131 """
132 Return a clone of the VM object with optionally modified parameters.
133 The clone is initially not alive and needs to be started using create().
134 Any parameters not passed to this function are copied from the source
135 VM.
136
137 @param name: Optional new VM name
138 @param params: Optional new VM creation parameters
139 @param qemu_path: Optional new path to qemu
140 @param image_dir: Optional new image dir
141 @param iso_dir: Optional new iso directory
142 """
143 if name == None:
144 name = self.name
145 if params == None:
146 params = self.params.copy()
147 if qemu_path == None:
148 qemu_path = self.qemu_path
149 if image_dir == None:
150 image_dir = self.image_dir
151 if iso_dir == None:
152 iso_dir = self.iso_dir
153 return VM(name, params, qemu_path, image_dir, iso_dir)
154
155
lmr6f669ce2009-05-31 19:02:42 +0000156 def verify_process_identity(self):
157 """
158 Make sure .pid really points to the original qemu process. If .pid
159 points to the same process that was created with the create method,
160 or to a dead process, return True. Otherwise return False.
161 """
162 if self.is_dead():
163 return True
164 filename = "/proc/%d/cmdline" % self.pid
165 if not os.path.exists(filename):
166 logging.debug("Filename %s does not exist" % filename)
167 return False
168 file = open(filename)
169 cmdline = file.read()
170 file.close()
171 if not self.qemu_path in cmdline:
172 return False
173 if not self.monitor_file_name in cmdline:
174 return False
175 return True
176
177
178 def make_qemu_command(self, name=None, params=None, qemu_path=None,
179 image_dir=None, iso_dir=None):
180 """
181 Generate a qemu command line. All parameters are optional. If a
182 parameter is not supplied, the corresponding value stored in the
183 class attributes is used.
184
185
186 @param name: The name of the object
187 @param params: A dict containing VM params
188 @param qemu_path: The path of the qemu binary
189 @param image_dir: The directory where images reside
190 @param iso_dir: The directory where ISOs reside
191
192
193 @note: The params dict should contain:
194 mem -- memory size in MBs
195 cdrom -- ISO filename to use with the qemu -cdrom parameter
196 (iso_dir is pre-pended to the ISO filename)
197 extra_params -- a string to append to the qemu command
198 ssh_port -- should be 22 for SSH, 23 for Telnet
199 images -- a list of image object names, separated by spaces
200 nics -- a list of NIC object names, separated by spaces
201
202 For each image in images:
203 drive_format -- string to pass as 'if' parameter for this
204 image (e.g. ide, scsi)
205 image_snapshot -- if yes, pass 'snapshot=on' to qemu for
206 this image
207 image_boot -- if yes, pass 'boot=on' to qemu for this image
208 In addition, all parameters required by get_image_filename.
209
210 For each NIC in nics:
211 nic_model -- string to pass as 'model' parameter for this
212 NIC (e.g. e1000)
213 """
214 if name == None:
215 name = self.name
216 if params == None:
217 params = self.params
218 if qemu_path == None:
219 qemu_path = self.qemu_path
220 if image_dir == None:
221 image_dir = self.image_dir
222 if iso_dir == None:
223 iso_dir = self.iso_dir
224
225 qemu_cmd = qemu_path
226 qemu_cmd += " -name '%s'" % name
227 qemu_cmd += " -monitor unix:%s,server,nowait" % self.monitor_file_name
228
229 for image_name in kvm_utils.get_sub_dict_names(params, "images"):
230 image_params = kvm_utils.get_sub_dict(params, image_name)
231 qemu_cmd += " -drive file=%s" % get_image_filename(image_params,
232 image_dir)
233 if image_params.get("drive_format"):
234 qemu_cmd += ",if=%s" % image_params.get("drive_format")
235 if image_params.get("image_snapshot") == "yes":
236 qemu_cmd += ",snapshot=on"
237 if image_params.get("image_boot") == "yes":
238 qemu_cmd += ",boot=on"
239
240 vlan = 0
241 for nic_name in kvm_utils.get_sub_dict_names(params, "nics"):
242 nic_params = kvm_utils.get_sub_dict(params, nic_name)
243 qemu_cmd += " -net nic,vlan=%d" % vlan
244 if nic_params.get("nic_model"):
245 qemu_cmd += ",model=%s" % nic_params.get("nic_model")
246 qemu_cmd += " -net user,vlan=%d" % vlan
247 vlan += 1
248
249 mem = params.get("mem")
250 if mem:
251 qemu_cmd += " -m %s" % mem
252
253 iso = params.get("cdrom")
254 if iso:
255 iso = os.path.join(iso_dir, iso)
256 qemu_cmd += " -cdrom %s" % iso
257
258 extra_params = params.get("extra_params")
259 if extra_params:
260 qemu_cmd += " %s" % extra_params
261
262 for redir_name in kvm_utils.get_sub_dict_names(params, "redirs"):
263 redir_params = kvm_utils.get_sub_dict(params, redir_name)
264 guest_port = int(redir_params.get("guest_port"))
265 host_port = self.get_port(guest_port)
266 qemu_cmd += " -redir tcp:%s::%s" % (host_port, guest_port)
267
268 if params.get("display") == "vnc":
269 qemu_cmd += " -vnc :%d" % (self.vnc_port - 5900)
270 elif params.get("display") == "sdl":
271 qemu_cmd += " -sdl"
272 elif params.get("display") == "nographic":
273 qemu_cmd += " -nographic"
274
275 return qemu_cmd
276
277
278 def create(self, name=None, params=None, qemu_path=None, image_dir=None,
279 iso_dir=None, for_migration=False, timeout=5.0):
280 """
281 Start the VM by running a qemu command.
282 All parameters are optional. The following applies to all parameters
283 but for_migration: If a parameter is not supplied, the corresponding
284 value stored in the class attributes is used, and if it is supplied,
285 it is stored for later use.
286
287 @param name: The name of the object
288 @param params: A dict containing VM params
289 @param qemu_path: The path of the qemu binary
290 @param image_dir: The directory where images reside
291 @param iso_dir: The directory where ISOs reside
292 @param for_migration: If True, start the VM with the -incoming
293 option
294 """
295 if name != None:
296 self.name = name
297 if params != None:
298 self.params = params
299 if qemu_path != None:
300 self.qemu_path = qemu_path
301 if image_dir != None:
302 self.image_dir = image_dir
303 if iso_dir != None:
304 self.iso_dir = iso_dir
305 name = self.name
306 params = self.params
307 qemu_path = self.qemu_path
308 image_dir = self.image_dir
309 iso_dir = self.iso_dir
310
311 # Verify the md5sum of the ISO image
312 iso = params.get("cdrom")
313 if iso:
314 iso = os.path.join(iso_dir, iso)
315 if not os.path.exists(iso):
316 logging.error("ISO file not found: %s" % iso)
317 return False
318 compare = False
319 if params.get("md5sum_1m"):
320 logging.debug("Comparing expected MD5 sum with MD5 sum of first"
321 "MB of ISO file...")
322 actual_md5sum = kvm_utils.md5sum_file(iso, 1048576)
323 expected_md5sum = params.get("md5sum_1m")
324 compare = True
325 elif params.get("md5sum"):
326 logging.debug("Comparing expected MD5 sum with MD5 sum of ISO"
327 " file...")
328 actual_md5sum = kvm_utils.md5sum_file(iso)
329 expected_md5sum = params.get("md5sum")
330 compare = True
331 if compare:
332 if actual_md5sum == expected_md5sum:
333 logging.debug("MD5 sums match")
334 else:
335 logging.error("Actual MD5 sum differs from expected one")
336 return False
337
lmr6f669ce2009-05-31 19:02:42 +0000338 # Handle port redirections
339 redir_names = kvm_utils.get_sub_dict_names(params, "redirs")
340 host_ports = kvm_utils.find_free_ports(5000, 6000, len(redir_names))
341 self.redirs = {}
342 for i in range(len(redir_names)):
343 redir_params = kvm_utils.get_sub_dict(params, redir_names[i])
344 guest_port = int(redir_params.get("guest_port"))
345 self.redirs[guest_port] = host_ports[i]
346
347 # Find available VNC port, if needed
348 if params.get("display") == "vnc":
349 self.vnc_port = kvm_utils.find_free_port(5900, 6000)
350
351 # Make qemu command
352 qemu_command = self.make_qemu_command()
353
354 # Is this VM supposed to accept incoming migrations?
355 if for_migration:
356 # Find available migration port
357 self.migration_port = kvm_utils.find_free_port(5200, 6000)
358 # Add -incoming option to the qemu command
359 qemu_command += " -incoming tcp:0:%d" % self.migration_port
360
361 logging.debug("Running qemu command:\n%s" % qemu_command)
362 (status, pid, output) = kvm_utils.run_bg(qemu_command, None,
363 logging.debug, "(qemu) ")
364
365 if status:
366 logging.debug("qemu exited with status %d" % status)
367 logging.error("VM could not be created -- qemu command"
368 " failed:\n%s" % qemu_command)
369 return False
370
371 self.pid = pid
372
373 if not kvm_utils.wait_for(self.is_alive, timeout, 0, 1):
374 logging.debug("VM is not alive for some reason")
375 logging.error("VM could not be created with"
376 " command:\n%s" % qemu_command)
377 self.destroy()
378 return False
379
380 logging.debug("VM appears to be alive with PID %d" % self.pid)
381
382 return True
383
384
385 def send_monitor_cmd(self, command, block=True, timeout=20.0):
386 """
387 Send command to the QEMU monitor.
388
389 Connect to the VM's monitor socket and wait for the (qemu) prompt.
390 If block is True, read output from the socket until the (qemu) prompt
391 is found again, or until timeout expires.
392
393 Return a tuple containing an integer indicating success or failure,
394 and the data read so far. The integer is 0 on success and 1 on failure.
395 A failure is any of the following cases: connection to the socket
396 failed, or the first (qemu) prompt could not be found, or block is
397 True and the second prompt could not be found.
398
399 @param command: Command that will be sent to the monitor
400 @param block: Whether the output from the socket will be read until
401 the timeout expires
402 @param timeout: Timeout (seconds) before giving up on reading from
403 socket
404 """
405 def read_up_to_qemu_prompt(s, timeout):
406 """
407 Read data from socket s until the (qemu) prompt is found.
408
409 If the prompt is found before timeout expires, return a tuple
410 containing True and the data read. Otherwise return a tuple
411 containing False and the data read so far.
412
413 @param s: Socket object
414 @param timeout: Time (seconds) before giving up trying to get the
415 qemu prompt.
416 """
417 o = ""
418 end_time = time.time() + timeout
419 while time.time() < end_time:
420 try:
421 o += s.recv(16384)
422 if o.splitlines()[-1].split()[-1] == "(qemu)":
423 return (True, o)
424 except:
425 time.sleep(0.01)
426 return (False, o)
427
428 # Connect to monitor
429 logging.debug("Sending monitor command: %s" % command)
430 try:
431 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
432 s.setblocking(False)
433 s.connect(self.monitor_file_name)
434 except:
435 logging.debug("Could not connect to monitor socket")
436 return (1, "")
437 status, data = read_up_to_qemu_prompt(s, timeout)
438 if not status:
439 s.close()
440 logging.debug("Could not find (qemu) prompt; output so far:" \
441 + kvm_utils.format_str_for_message(data))
442 return (1, "")
443 # Send command
444 s.sendall(command + "\n")
445 # Receive command output
446 data = ""
447 if block:
448 status, data = read_up_to_qemu_prompt(s, timeout)
449 data = "\n".join(data.splitlines()[1:])
450 if not status:
451 s.close()
452 logging.debug("Could not find (qemu) prompt after command;"
453 " output so far: %s",
454 kvm_utils.format_str_for_message(data))
455 return (1, data)
456 s.close()
457 return (0, data)
458
459
460 def destroy(self, gracefully=True):
461 """
462 Destroy the VM.
463
464 If gracefully is True, first attempt to kill the VM via SSH/Telnet
465 with a shutdown command. Then, attempt to destroy the VM via the
466 monitor with a 'quit' command. If that fails, send SIGKILL to the
467 qemu process.
468
469 @param gracefully: Whether an attempt will be made to end the VM
470 using monitor command before trying to kill the qemu process
471 or not.
472 """
473 # Is it already dead?
474 if self.is_dead():
475 logging.debug("VM is already down")
476 return
477
478 logging.debug("Destroying VM with PID %d..." % self.pid)
479
480 if gracefully and self.params.get("cmd_shutdown"):
481 # Try to destroy with SSH command
482 logging.debug("Trying to shutdown VM with SSH command...")
483 (status, output) = self.ssh(self.params.get("cmd_shutdown"))
484 # Was the command sent successfully?
485 if status == 0:
486 #if self.ssh(self.params.get("cmd_shutdown")):
487 logging.debug("Shutdown command sent; Waiting for VM to go"
488 "down...")
489 if kvm_utils.wait_for(self.is_dead, 60, 1, 1):
490 logging.debug("VM is down")
491 self.pid = None
492 return
493
494 # Try to destroy with a monitor command
495 logging.debug("Trying to kill VM with monitor command...")
496 (status, output) = self.send_monitor_cmd("quit", block=False)
497 # Was the command sent successfully?
498 if status == 0:
499 # Wait for the VM to be really dead
500 if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
501 logging.debug("VM is down")
502 self.pid = None
503 return
504
505 # If the VM isn't dead yet...
506 logging.debug("Cannot quit normally; Sending a kill to close the"
507 " deal...")
508 kvm_utils.safe_kill(self.pid, 9)
509 # Wait for the VM to be really dead
510 if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
511 logging.debug("VM is down")
512 self.pid = None
513 return
514
515 logging.error("We have a zombie! PID %d is a zombie!" % self.pid)
516
517
518 def is_alive(self):
519 """
520 Return True if the VM's monitor is responsive.
521 """
522 # Check if the process exists
523 if not kvm_utils.pid_exists(self.pid):
524 return False
525 # Try sending a monitor command
526 (status, output) = self.send_monitor_cmd("help")
527 if status:
528 return False
529 return True
530
531
532 def is_dead(self):
533 """
534 Return True iff the VM's PID does not exist.
535 """
536 return not kvm_utils.pid_exists(self.pid)
537
538
539 def get_params(self):
540 """
541 Return the VM's params dict. Most modified params take effect only
542 upon VM.create().
543 """
544 return self.params
545
546
547 def get_address(self):
548 """
549 Return the guest's address in host space.
550
551 If port redirection is used, return 'localhost' (the guest has no IP
552 address of its own). Otherwise return the guest's IP address.
553 """
554 # Currently redirection is always used, so return 'localhost'
555 return "localhost"
556
557
558 def get_port(self, port):
559 """
560 Return the port in host space corresponding to port in guest space.
561
562 @param port: Port number in host space.
563 @return: If port redirection is used, return the host port redirected
564 to guest port port. Otherwise return port.
565 """
566 # Currently redirection is always used, so use the redirs dict
567 if self.redirs.has_key(port):
568 return self.redirs[port]
569 else:
570 logging.debug("Warning: guest port %s requested but not"
571 " redirected" % port)
572 return None
573
574
575 def is_sshd_running(self, timeout=10):
576 """
577 Return True iff the guest's SSH port is responsive.
578
579 @param timeout: Time (seconds) before giving up checking the SSH daemon
580 responsiveness.
581 """
582 address = self.get_address()
583 port = self.get_port(int(self.params.get("ssh_port")))
584 if not port:
585 return False
586 return kvm_utils.is_sshd_running(address, port, timeout=timeout)
587
588
589 def ssh_login(self, timeout=10):
590 """
591 Log into the guest via SSH/Telnet.
592 If timeout expires while waiting for output from the guest (e.g. a
593 password prompt or a shell prompt) -- fail.
594
595 @param timeout: Time (seconds) before giving up logging into the
596 guest.
597 @return: kvm_spawn object on success and None on failure.
598 """
599 username = self.params.get("username", "")
600 password = self.params.get("password", "")
601 prompt = self.params.get("ssh_prompt", "[\#\$]")
602 use_telnet = self.params.get("use_telnet") == "yes"
603 address = self.get_address()
604 port = self.get_port(int(self.params.get("ssh_port")))
605 if not port:
606 return None
607
608 if use_telnet:
609 session = kvm_utils.telnet(address, port, username, password,
610 prompt, timeout)
611 else:
612 session = kvm_utils.ssh(address, port, username, password,
613 prompt, timeout)
614 if session:
615 session.set_status_test_command(self.params.get("ssh_status_test_"
616 "command", ""))
617 return session
618
619
620 def scp_to_remote(self, local_path, remote_path, timeout=300):
621 """
622 Transfer files to the guest via SCP.
623
624 @param local_path: Host path
625 @param remote_path: Guest path
626 @param timeout: Time (seconds) before giving up on doing the remote
627 copy.
628 """
629 username = self.params.get("username", "")
630 password = self.params.get("password", "")
631 address = self.get_address()
632 port = self.get_port(int(self.params.get("ssh_port")))
633 if not port:
634 return None
635 return kvm_utils.scp_to_remote(address, port, username, password,
636 local_path, remote_path, timeout)
637
638
639 def scp_from_remote(self, remote_path, local_path, timeout=300):
640 """
641 Transfer files from the guest via SCP.
642
643 @param local_path: Guest path
644 @param remote_path: Host path
645 @param timeout: Time (seconds) before giving up on doing the remote
646 copy.
647 """
648 username = self.params.get("username", "")
649 password = self.params.get("password", "")
650 address = self.get_address()
651 port = self.get_port(int(self.params.get("ssh_port")))
652 if not port:
653 return None
654 return kvm_utils.scp_from_remote(address, port, username, password,
655 remote_path, local_path, timeout)
656
657
658 def ssh(self, command, timeout=10):
659 """
660 Login via SSH/Telnet and send a command.
661
662 @command: Command that will be sent.
663 @timeout: Time before giving up waiting on a status return.
664 @return: A tuple (status, output). status is 0 on success and 1 on
665 failure.
666 """
667 session = self.ssh_login(timeout)
668 if not session:
669 return (1, "")
670
671 logging.debug("Sending command: %s" % command)
672 session.sendline(command)
673 output = session.read_nonblocking(1.0)
674 session.close()
675
676 return (0, output)
677
678
679 def send_key(self, keystr):
680 """
681 Send a key event to the VM.
682
683 @param: keystr: A key event string (e.g. "ctrl-alt-delete")
684 """
685 # For compatibility with versions of QEMU that do not recognize all
686 # key names: replace keyname with the hex value from the dict, which
687 # QEMU will definitely accept
688 dict = { "comma": "0x33",
689 "dot": "0x34",
690 "slash": "0x35" }
691 for key in dict.keys():
692 keystr = keystr.replace(key, dict[key])
693 self.send_monitor_cmd("sendkey %s 1" % keystr)
694 time.sleep(0.2)
695
696
697 def send_string(self, str):
698 """
699 Send a string to the VM.
700
701 @param str: String, that must consist of alphanumeric characters only.
702 Capital letters are allowed.
703 """
704 for char in str:
705 if char.isupper():
706 self.send_key("shift-%s" % char.lower())
707 else:
708 self.send_key(char)