2 KVM test utility functions.
4 @copyright: 2008-2009 Red Hat Inc.
7 import time
, string
, random
, socket
, os
, signal
, re
, logging
, commands
, cPickle
8 import fcntl
, shelve
, ConfigParser
, threading
, sys
, UserDict
, inspect
, tarfile
10 from autotest_lib
.client
.bin
import utils
, os_dep
11 from autotest_lib
.client
.common_lib
import error
, logging_config
12 import rss_client
, aexpect
17 KOJI_INSTALLED
= False
19 # From include/linux/sockios.h
20 SIOCSIFHWADDR
= 0x8924
21 SIOCGIFHWADDR
= 0x8927
25 # From linux/include/linux/if_tun.h
26 TUNSETIFF
= 0x400454ca
27 TUNGETIFF
= 0x800454d2
28 TUNGETFEATURES
= 0x800454cf
34 def _lock_file(filename
):
35 f
= open(filename
, "w")
36 fcntl
.lockf(f
, fcntl
.LOCK_EX
)
41 fcntl
.lockf(f
, fcntl
.LOCK_UN
)
47 Tests whether a given object is a VM object.
49 @param obj: Python object.
51 return obj
.__class
__.__name
__ == "VM"
54 class NetError(Exception):
58 class TAPModuleError(NetError
):
59 def __init__(self
, devname
, action
="open", details
=None):
60 NetError
.__init
__(self
, devname
)
61 self
.devname
= devname
62 self
.details
= details
65 e_msg
= "Can't %s %s" % (self
.action
, self
.devname
)
66 if self
.details
is not None:
67 e_msg
+= " : %s" % self
.details
71 class TAPNotExistError(NetError
):
72 def __init__(self
, ifname
):
73 NetError
.__init
__(self
, ifname
)
77 return "Interface %s does not exist" % self
.ifname
80 class TAPCreationError(NetError
):
81 def __init__(self
, ifname
, details
=None):
82 NetError
.__init
__(self
, ifname
, details
)
84 self
.details
= details
87 e_msg
= "Cannot create TAP device %s" % self
.ifname
88 if self
.details
is not None:
89 e_msg
+= ": %s" % self
.details
93 class TAPBringUpError(NetError
):
94 def __init__(self
, ifname
):
95 NetError
.__init
__(self
, ifname
)
99 return "Cannot bring up TAP %s" % self
.ifname
102 class BRAddIfError(NetError
):
103 def __init__(self
, ifname
, brname
, details
):
104 NetError
.__init
__(self
, ifname
, brname
, details
)
107 self
.details
= details
110 return ("Can not add if %s to bridge %s: %s" %
111 (self
.ifname
, self
.brname
, self
.details
))
114 class HwAddrSetError(NetError
):
115 def __init__(self
, ifname
, mac
):
116 NetError
.__init
__(self
, ifname
, mac
)
121 return "Can not set mac %s to interface %s" % (self
.mac
, self
.ifname
)
124 class HwAddrGetError(NetError
):
125 def __init__(self
, ifname
):
126 NetError
.__init
__(self
, ifname
)
130 return "Can not get mac of interface %s" % self
.ifname
133 class Env(UserDict
.IterableUserDict
):
135 A dict-like object containing global objects used by tests.
137 def __init__(self
, filename
=None, version
=0):
139 Create an empty Env object or load an existing one from a file.
141 If the version recorded in the file is lower than version, or if some
142 error occurs during unpickling, or if filename is not supplied,
143 create an empty Env object.
145 @param filename: Path to an env file.
146 @param version: Required env version (int).
148 UserDict
.IterableUserDict
.__init
__(self
)
149 empty
= {"version": version
}
151 self
._filename
= filename
153 f
= open(filename
, "r")
154 env
= cPickle
.load(f
)
156 if env
.get("version", 0) >= version
:
159 logging
.warn("Incompatible env file found. Not using it.")
161 # Almost any exception can be raised during unpickling, so let's
170 def save(self
, filename
=None):
172 Pickle the contents of the Env object into a file.
174 @param filename: Filename to pickle the dict into. If not supplied,
175 use the filename from which the dict was loaded.
177 filename
= filename
or self
._filename
178 f
= open(filename
, "w")
179 cPickle
.dump(self
.data
, f
)
183 def get_all_vms(self
):
185 Return a list of all VM objects in this Env object.
187 return [o
for o
in self
.values() if is_vm(o
)]
190 def get_vm(self
, name
):
192 Return a VM object by its name.
194 @param name: VM name.
196 return self
.get("vm__%s" % name
)
199 def register_vm(self
, name
, vm
):
201 Register a VM in this Env object.
203 @param name: VM name.
204 @param vm: VM object.
206 self
["vm__%s" % name
] = vm
209 def unregister_vm(self
, name
):
213 @param name: VM name.
215 del self
["vm__%s" % name
]
218 def register_installer(self
, installer
):
220 Register a installer that was just run
222 The installer will be available for other tests, so that
223 information about the installed KVM modules and qemu-kvm can be used by
226 self
['last_installer'] = installer
229 def previous_installer(self
):
231 Return the last installer that was registered
233 return self
.get('last_installer')
236 class Params(UserDict
.IterableUserDict
):
238 A dict-like object passed to every test.
240 def objects(self
, key
):
242 Return the names of objects defined using a given key.
244 @param key: The name of the key whose value lists the objects
247 return self
.get(key
, "").split()
250 def object_params(self
, obj_name
):
252 Return a dict-like object containing the parameters of an individual
255 This method behaves as follows: the suffix '_' + obj_name is removed
256 from all key names that have it. Other key names are left unchanged.
257 The values of keys with the suffix overwrite the values of their
260 @param obj_name: The name of the object (objects are listed by the
263 suffix
= "_" + obj_name
264 new_dict
= self
.copy()
266 if key
.endswith(suffix
):
267 new_key
= key
.split(suffix
)[0]
268 new_dict
[new_key
] = self
[key
]
272 # Functions related to MAC/IP addresses
274 def _open_mac_pool(lock_mode
):
275 lock_file
= open("/tmp/mac_lock", "w+")
276 fcntl
.lockf(lock_file
, lock_mode
)
277 pool
= shelve
.open("/tmp/address_pool")
278 return pool
, lock_file
281 def _close_mac_pool(pool
, lock_file
):
283 fcntl
.lockf(lock_file
, fcntl
.LOCK_UN
)
287 def _generate_mac_address_prefix(mac_pool
):
289 Generate a random MAC address prefix and add it to the MAC pool dictionary.
290 If there's a MAC prefix there already, do not update the MAC pool and just
291 return what's in there. By convention we will set KVM autotest MAC
292 addresses to start with 0x9a.
294 @param mac_pool: The MAC address pool object.
295 @return: The MAC address prefix.
297 if "prefix" in mac_pool
:
298 prefix
= mac_pool
["prefix"]
300 r
= random
.SystemRandom()
301 prefix
= "9a:%02x:%02x:%02x:" % (r
.randint(0x00, 0xff),
302 r
.randint(0x00, 0xff),
303 r
.randint(0x00, 0xff))
304 mac_pool
["prefix"] = prefix
308 def generate_mac_address(vm_instance
, nic_index
):
310 Randomly generate a MAC address and add it to the MAC address pool.
312 Try to generate a MAC address based on a randomly generated MAC address
313 prefix and add it to a persistent dictionary.
314 key = VM instance + NIC index, value = MAC address
315 e.g. {'20100310-165222-Wt7l:0': '9a:5d:94:6a:9b:f9'}
317 @param vm_instance: The instance attribute of a VM.
318 @param nic_index: The index of the NIC.
319 @return: MAC address string.
321 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_EX
)
322 key
= "%s:%s" % (vm_instance
, nic_index
)
326 prefix
= _generate_mac_address_prefix(mac_pool
)
327 r
= random
.SystemRandom()
328 while key
not in mac_pool
:
329 mac
= prefix
+ "%02x:%02x" % (r
.randint(0x00, 0xff),
330 r
.randint(0x00, 0xff))
331 if mac
in mac_pool
.values():
334 _close_mac_pool(mac_pool
, lock_file
)
338 def free_mac_address(vm_instance
, nic_index
):
340 Remove a MAC address from the address pool.
342 @param vm_instance: The instance attribute of a VM.
343 @param nic_index: The index of the NIC.
345 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_EX
)
346 key
= "%s:%s" % (vm_instance
, nic_index
)
349 _close_mac_pool(mac_pool
, lock_file
)
352 def set_mac_address(vm_instance
, nic_index
, mac
):
354 Set a MAC address in the pool.
356 @param vm_instance: The instance attribute of a VM.
357 @param nic_index: The index of the NIC.
359 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_EX
)
360 mac_pool
["%s:%s" % (vm_instance
, nic_index
)] = mac
361 _close_mac_pool(mac_pool
, lock_file
)
364 def get_mac_address(vm_instance
, nic_index
):
366 Return a MAC address from the pool.
368 @param vm_instance: The instance attribute of a VM.
369 @param nic_index: The index of the NIC.
370 @return: MAC address string.
372 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_SH
)
373 mac
= mac_pool
.get("%s:%s" % (vm_instance
, nic_index
))
374 _close_mac_pool(mac_pool
, lock_file
)
378 def verify_ip_address_ownership(ip
, macs
, timeout
=10.0):
380 Use arping and the ARP cache to make sure a given IP address belongs to one
381 of the given MAC addresses.
383 @param ip: An IP address.
384 @param macs: A list or tuple of MAC addresses.
385 @return: True iff ip is assigned to a MAC address in macs.
387 # Compile a regex that matches the given IP address and any of the given
389 mac_regex
= "|".join("(%s)" % mac
for mac
in macs
)
390 regex
= re
.compile(r
"\b%s\b.*\b(%s)\b" % (ip
, mac_regex
), re
.IGNORECASE
)
392 # Check the ARP cache
393 o
= commands
.getoutput("%s -n" % find_command("arp"))
397 # Get the name of the bridge device for arping
398 o
= commands
.getoutput("%s route get %s" % (find_command("ip"), ip
))
399 dev
= re
.findall("dev\s+\S+", o
, re
.IGNORECASE
)
402 dev
= dev
[0].split()[-1]
404 # Send an ARP request
405 o
= commands
.getoutput("%s -f -c 3 -I %s %s" %
406 (find_command("arping"), dev
, ip
))
407 return bool(regex
.search(o
))
410 # Utility functions for dealing with external processes
412 def find_command(cmd
):
413 for dir in ["/usr/local/sbin", "/usr/local/bin",
414 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]:
415 file = os
.path
.join(dir, cmd
)
416 if os
.path
.exists(file):
418 raise ValueError('Missing command: %s' % cmd
)
423 Return True if a given PID exists.
425 @param pid: Process ID number.
434 def safe_kill(pid
, signal
):
436 Attempt to send a signal to a given process that may or may not exist.
438 @param signal: Signal number.
447 def kill_process_tree(pid
, sig
=signal
.SIGKILL
):
448 """Signal a process and all of its children.
450 If the process does not exist -- return.
452 @param pid: The pid of the process to signal.
453 @param sig: The signal to send to the processes.
455 if not safe_kill(pid
, signal
.SIGSTOP
):
457 children
= commands
.getoutput("ps --ppid=%d -o pid=" % pid
).split()
458 for child
in children
:
459 kill_process_tree(int(child
), sig
)
461 safe_kill(pid
, signal
.SIGCONT
)
464 def get_git_branch(repository
, branch
, srcdir
, commit
=None, lbranch
=None):
466 Retrieves a given git code repository.
468 @param repository: Git repository URL
470 logging
.info("Fetching git [REP '%s' BRANCH '%s' COMMIT '%s'] -> %s",
471 repository
, branch
, commit
, srcdir
)
472 if not os
.path
.exists(srcdir
):
476 if os
.path
.exists(".git"):
477 utils
.system("git reset --hard")
479 utils
.system("git init")
484 utils
.system("git fetch -q -f -u -t %s %s:%s" %
485 (repository
, branch
, lbranch
))
486 utils
.system("git checkout %s" % lbranch
)
488 utils
.system("git checkout %s" % commit
)
490 h
= utils
.system_output('git log --pretty=format:"%H" -1')
492 desc
= "tag %s" % utils
.system_output("git describe")
493 except error
.CmdError
:
494 desc
= "no tag found"
496 logging
.info("Commit hash for %s is %s (%s)", repository
, h
.strip(), desc
)
500 def check_kvm_source_dir(source_dir
):
502 Inspects the kvm source directory and verifies its disposition. In some
503 occasions build may be dependant on the source directory disposition.
504 The reason why the return codes are numbers is that we might have more
505 changes on the source directory layout, so it's not scalable to just use
506 strings like 'old_repo', 'new_repo' and such.
508 @param source_dir: Source code path that will be inspected.
511 has_qemu_dir
= os
.path
.isdir('qemu')
512 has_kvm_dir
= os
.path
.isdir('kvm')
514 logging
.debug("qemu directory detected, source dir layout 1")
516 if has_kvm_dir
and not has_qemu_dir
:
517 logging
.debug("kvm directory detected, source dir layout 2")
520 raise error
.TestError("Unknown source dir layout, cannot proceed.")
523 # Functions and classes used for logging into guests and transferring files
525 class LoginError(Exception):
526 def __init__(self
, msg
, output
):
527 Exception.__init
__(self
, msg
, output
)
532 return "%s (output: %r)" % (self
.msg
, self
.output
)
535 class LoginAuthenticationError(LoginError
):
539 class LoginTimeoutError(LoginError
):
540 def __init__(self
, output
):
541 LoginError
.__init
__(self
, "Login timeout expired", output
)
544 class LoginProcessTerminatedError(LoginError
):
545 def __init__(self
, status
, output
):
546 LoginError
.__init
__(self
, None, output
)
550 return ("Client process terminated (status: %s, output: %r)" %
551 (self
.status
, self
.output
))
554 class LoginBadClientError(LoginError
):
555 def __init__(self
, client
):
556 LoginError
.__init
__(self
, None, None)
560 return "Unknown remote shell client: %r" % self
.client
563 class SCPError(Exception):
564 def __init__(self
, msg
, output
):
565 Exception.__init
__(self
, msg
, output
)
570 return "%s (output: %r)" % (self
.msg
, self
.output
)
573 class SCPAuthenticationError(SCPError
):
577 class SCPAuthenticationTimeoutError(SCPAuthenticationError
):
578 def __init__(self
, output
):
579 SCPAuthenticationError
.__init
__(self
, "Authentication timeout expired",
583 class SCPTransferTimeoutError(SCPError
):
584 def __init__(self
, output
):
585 SCPError
.__init
__(self
, "Transfer timeout expired", output
)
588 class SCPTransferFailedError(SCPError
):
589 def __init__(self
, status
, output
):
590 SCPError
.__init
__(self
, None, output
)
594 return ("SCP transfer failed (status: %s, output: %r)" %
595 (self
.status
, self
.output
))
598 def _remote_login(session
, username
, password
, prompt
, timeout
=10):
600 Log into a remote host (guest) using SSH or Telnet. Wait for questions
601 and provide answers. If timeout expires while waiting for output from the
602 child (e.g. a password prompt or a shell prompt) -- fail.
604 @brief: Log into a remote host (guest) using SSH or Telnet.
606 @param session: An Expect or ShellSession instance to operate on
607 @param username: The username to send in reply to a login prompt
608 @param password: The password to send in reply to a password prompt
609 @param prompt: The shell prompt that indicates a successful login
610 @param timeout: The maximal time duration (in seconds) to wait for each
611 step of the login procedure (i.e. the "Are you sure" prompt, the
612 password prompt, the shell prompt, etc)
613 @raise LoginTimeoutError: If timeout expires
614 @raise LoginAuthenticationError: If authentication fails
615 @raise LoginProcessTerminatedError: If the client terminates during login
616 @raise LoginError: If some other error occurs
618 password_prompt_count
= 0
619 login_prompt_count
= 0
623 match
, text
= session
.read_until_last_line_matches(
624 [r
"[Aa]re you sure", r
"[Pp]assword:\s*$", r
"[Ll]ogin:\s*$",
625 r
"[Cc]onnection.*closed", r
"[Cc]onnection.*refused",
626 r
"[Pp]lease wait", r
"[Ww]arning", prompt
],
627 timeout
=timeout
, internal_timeout
=0.5)
628 if match
== 0: # "Are you sure you want to continue connecting"
629 logging
.debug("Got 'Are you sure...', sending 'yes'")
630 session
.sendline("yes")
632 elif match
== 1: # "password:"
633 if password_prompt_count
== 0:
634 logging
.debug("Got password prompt, sending '%s'", password
)
635 session
.sendline(password
)
636 password_prompt_count
+= 1
639 raise LoginAuthenticationError("Got password prompt twice",
641 elif match
== 2: # "login:"
642 if login_prompt_count
== 0 and password_prompt_count
== 0:
643 logging
.debug("Got username prompt; sending '%s'", username
)
644 session
.sendline(username
)
645 login_prompt_count
+= 1
648 if login_prompt_count
> 0:
649 msg
= "Got username prompt twice"
651 msg
= "Got username prompt after password prompt"
652 raise LoginAuthenticationError(msg
, text
)
653 elif match
== 3: # "Connection closed"
654 raise LoginError("Client said 'connection closed'", text
)
655 elif match
== 4: # "Connection refused"
656 raise LoginError("Client said 'connection refused'", text
)
657 elif match
== 5: # "Please wait"
658 logging
.debug("Got 'Please wait'")
661 elif match
== 6: # "Warning added RSA"
662 logging
.debug("Got 'Warning added RSA to known host list")
664 elif match
== 7: # prompt
665 logging
.debug("Got shell prompt -- logged in")
667 except aexpect
.ExpectTimeoutError
, e
:
668 raise LoginTimeoutError(e
.output
)
669 except aexpect
.ExpectProcessTerminatedError
, e
:
670 raise LoginProcessTerminatedError(e
.status
, e
.output
)
673 def remote_login(client
, host
, port
, username
, password
, prompt
, linesep
="\n",
674 log_filename
=None, timeout
=10):
676 Log into a remote host (guest) using SSH/Telnet/Netcat.
678 @param client: The client to use ('ssh', 'telnet' or 'nc')
679 @param host: Hostname or IP address
680 @param port: Port to connect to
681 @param username: Username (if required)
682 @param password: Password (if required)
683 @param prompt: Shell prompt (regular expression)
684 @param linesep: The line separator to use when sending lines
685 (e.g. '\\n' or '\\r\\n')
686 @param log_filename: If specified, log all output to this file
687 @param timeout: The maximal time duration (in seconds) to wait for
688 each step of the login procedure (i.e. the "Are you sure" prompt
689 or the password prompt)
690 @raise LoginBadClientError: If an unknown client is requested
691 @raise: Whatever _remote_login() raises
692 @return: A ShellSession object.
695 cmd
= ("ssh -o UserKnownHostsFile=/dev/null "
696 "-o PreferredAuthentications=password -p %s %s@%s" %
697 (port
, username
, host
))
698 elif client
== "telnet":
699 cmd
= "telnet -l %s %s %s" % (username
, host
, port
)
701 cmd
= "nc %s %s" % (host
, port
)
703 raise LoginBadClientError(client
)
705 logging
.debug("Trying to login with command '%s'", cmd
)
706 session
= aexpect
.ShellSession(cmd
, linesep
=linesep
, prompt
=prompt
)
708 _remote_login(session
, username
, password
, prompt
, timeout
)
713 session
.set_output_func(log_line
)
714 session
.set_output_params((log_filename
,))
718 def wait_for_login(client
, host
, port
, username
, password
, prompt
, linesep
="\n",
719 log_filename
=None, timeout
=240, internal_timeout
=10):
721 Make multiple attempts to log into a remote host (guest) until one succeeds
724 @param timeout: Total time duration to wait for a successful login
725 @param internal_timeout: The maximal time duration (in seconds) to wait for
726 each step of the login procedure (e.g. the "Are you sure" prompt
727 or the password prompt)
729 @raise: Whatever remote_login() raises
730 @return: A ShellSession object.
732 logging
.debug("Attempting to log into %s:%s using %s (timeout %ds)",
733 host
, port
, client
, timeout
)
734 end_time
= time
.time() + timeout
735 while time
.time() < end_time
:
737 return remote_login(client
, host
, port
, username
, password
, prompt
,
738 linesep
, log_filename
, internal_timeout
)
739 except LoginError
, e
:
742 # Timeout expired; try one more time but don't catch exceptions
743 return remote_login(client
, host
, port
, username
, password
, prompt
,
744 linesep
, log_filename
, internal_timeout
)
747 def _remote_scp(session
, password_list
, transfer_timeout
=600, login_timeout
=10):
749 Transfer file(s) to a remote host (guest) using SCP. Wait for questions
750 and provide answers. If login_timeout expires while waiting for output
751 from the child (e.g. a password prompt), fail. If transfer_timeout expires
752 while waiting for the transfer to complete, fail.
754 @brief: Transfer files using SCP, given a command line.
756 @param session: An Expect or ShellSession instance to operate on
757 @param password_list: Password list to send in reply to the password prompt
758 @param transfer_timeout: The time duration (in seconds) to wait for the
759 transfer to complete.
760 @param login_timeout: The maximal time duration (in seconds) to wait for
761 each step of the login procedure (i.e. the "Are you sure" prompt or
763 @raise SCPAuthenticationError: If authentication fails
764 @raise SCPTransferTimeoutError: If the transfer fails to complete in time
765 @raise SCPTransferFailedError: If the process terminates with a nonzero
767 @raise SCPError: If some other error occurs
769 password_prompt_count
= 0
770 timeout
= login_timeout
771 authentication_done
= False
773 scp_type
= len(password_list
)
777 match
, text
= session
.read_until_last_line_matches(
778 [r
"[Aa]re you sure", r
"[Pp]assword:\s*$", r
"lost connection"],
779 timeout
=timeout
, internal_timeout
=0.5)
780 if match
== 0: # "Are you sure you want to continue connecting"
781 logging
.debug("Got 'Are you sure...', sending 'yes'")
782 session
.sendline("yes")
784 elif match
== 1: # "password:"
785 if password_prompt_count
== 0:
786 logging
.debug("Got password prompt, sending '%s'" %
787 password_list
[password_prompt_count
])
788 session
.sendline(password_list
[password_prompt_count
])
789 password_prompt_count
+= 1
790 timeout
= transfer_timeout
792 authentication_done
= True
794 elif password_prompt_count
== 1 and scp_type
== 2:
795 logging
.debug("Got password prompt, sending '%s'" %
796 password_list
[password_prompt_count
])
797 session
.sendline(password_list
[password_prompt_count
])
798 password_prompt_count
+= 1
799 timeout
= transfer_timeout
800 authentication_done
= True
803 raise SCPAuthenticationError("Got password prompt twice",
805 elif match
== 2: # "lost connection"
806 raise SCPError("SCP client said 'lost connection'", text
)
807 except aexpect
.ExpectTimeoutError
, e
:
808 if authentication_done
:
809 raise SCPTransferTimeoutError(e
.output
)
811 raise SCPAuthenticationTimeoutError(e
.output
)
812 except aexpect
.ExpectProcessTerminatedError
, e
:
814 logging
.debug("SCP process terminated with status 0")
817 raise SCPTransferFailedError(e
.status
, e
.output
)
820 def remote_scp(command
, password_list
, log_filename
=None, transfer_timeout
=600,
823 Transfer file(s) to a remote host (guest) using SCP.
825 @brief: Transfer files using SCP, given a command line.
827 @param command: The command to execute
828 (e.g. "scp -r foobar root@localhost:/tmp/").
829 @param password_list: Password list to send in reply to a password prompt.
830 @param log_filename: If specified, log all output to this file
831 @param transfer_timeout: The time duration (in seconds) to wait for the
832 transfer to complete.
833 @param login_timeout: The maximal time duration (in seconds) to wait for
834 each step of the login procedure (i.e. the "Are you sure" prompt
835 or the password prompt)
836 @raise: Whatever _remote_scp() raises
838 logging
.debug("Trying to SCP with command '%s', timeout %ss",
839 command
, transfer_timeout
)
841 output_func
= log_line
842 output_params
= (log_filename
,)
846 session
= aexpect
.Expect(command
,
847 output_func
=output_func
,
848 output_params
=output_params
)
850 _remote_scp(session
, password_list
, transfer_timeout
, login_timeout
)
855 def scp_to_remote(host
, port
, username
, password
, local_path
, remote_path
,
856 log_filename
=None, timeout
=600):
858 Copy files to a remote host (guest) through scp.
860 @param host: Hostname or IP address
861 @param username: Username (if required)
862 @param password: Password (if required)
863 @param local_path: Path on the local machine where we are copying from
864 @param remote_path: Path on the remote machine where we are copying to
865 @param log_filename: If specified, log all output to this file
866 @param timeout: The time duration (in seconds) to wait for the transfer
868 @raise: Whatever remote_scp() raises
870 command
= ("scp -v -o UserKnownHostsFile=/dev/null "
871 "-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" %
872 (port
, local_path
, username
, host
, remote_path
))
874 password_list
.append(password
)
875 return remote_scp(command
, password_list
, log_filename
, timeout
)
879 def scp_from_remote(host
, port
, username
, password
, remote_path
, local_path
,
880 log_filename
=None, timeout
=600):
882 Copy files from a remote host (guest).
884 @param host: Hostname or IP address
885 @param username: Username (if required)
886 @param password: Password (if required)
887 @param local_path: Path on the local machine where we are copying from
888 @param remote_path: Path on the remote machine where we are copying to
889 @param log_filename: If specified, log all output to this file
890 @param timeout: The time duration (in seconds) to wait for the transfer
892 @raise: Whatever remote_scp() raises
894 command
= ("scp -v -o UserKnownHostsFile=/dev/null "
895 "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" %
896 (port
, username
, host
, remote_path
, local_path
))
898 password_list
.append(password
)
899 remote_scp(command
, password_list
, log_filename
, timeout
)
902 def scp_between_remotes(src
, dst
, port
, s_passwd
, d_passwd
, s_name
, d_name
,
903 s_path
, d_path
, log_filename
=None, timeout
=600):
905 Copy files from a remote host (guest) to another remote host (guest).
907 @param src/dst: Hostname or IP address of src and dst
908 @param s_name/d_name: Username (if required)
909 @param s_passwd/d_passwd: Password (if required)
910 @param s_path/d_path: Path on the remote machine where we are copying
912 @param log_filename: If specified, log all output to this file
913 @param timeout: The time duration (in seconds) to wait for the transfer
916 @return: True on success and False on failure.
918 command
= ("scp -v -o UserKnownHostsFile=/dev/null -o "
919 "PreferredAuthentications=password -r -P %s %s@%s:%s %s@%s:%s" %
920 (port
, s_name
, src
, s_path
, d_name
, dst
, d_path
))
922 password_list
.append(s_passwd
)
923 password_list
.append(d_passwd
)
924 return remote_scp(command
, password_list
, log_filename
, timeout
)
927 def copy_files_to(address
, client
, username
, password
, port
, local_path
,
928 remote_path
, log_filename
=None, verbose
=False, timeout
=600):
930 Copy files to a remote host (guest) using the selected client.
932 @param client: Type of transfer client
933 @param username: Username (if required)
934 @param password: Password (if requried)
935 @param local_path: Path on the local machine where we are copying from
936 @param remote_path: Path on the remote machine where we are copying to
937 @param address: Address of remote host(guest)
938 @param log_filename: If specified, log all output to this file (SCP only)
939 @param verbose: If True, log some stats using logging.debug (RSS only)
940 @param timeout: The time duration (in seconds) to wait for the transfer to
942 @raise: Whatever remote_scp() raises
945 scp_to_remote(address
, port
, username
, password
, local_path
,
946 remote_path
, log_filename
, timeout
)
947 elif client
== "rss":
950 log_func
= logging
.debug
951 c
= rss_client
.FileUploadClient(address
, port
, log_func
)
952 c
.upload(local_path
, remote_path
, timeout
)
956 def copy_files_from(address
, client
, username
, password
, port
, remote_path
,
957 local_path
, log_filename
=None, verbose
=False, timeout
=600):
959 Copy files from a remote host (guest) using the selected client.
961 @param client: Type of transfer client
962 @param username: Username (if required)
963 @param password: Password (if requried)
964 @param remote_path: Path on the remote machine where we are copying from
965 @param local_path: Path on the local machine where we are copying to
966 @param address: Address of remote host(guest)
967 @param log_filename: If specified, log all output to this file (SCP only)
968 @param verbose: If True, log some stats using logging.debug (RSS only)
969 @param timeout: The time duration (in seconds) to wait for the transfer to
971 @raise: Whatever remote_scp() raises
974 scp_from_remote(address
, port
, username
, password
, remote_path
,
975 local_path
, log_filename
, timeout
)
976 elif client
== "rss":
979 log_func
= logging
.debug
980 c
= rss_client
.FileDownloadClient(address
, port
, log_func
)
981 c
.download(remote_path
, local_path
, timeout
)
985 # The following are utility functions related to ports.
987 def is_port_free(port
, address
):
989 Return True if the given port is available for use.
991 @param port: Port number
995 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
996 if address
== "localhost":
997 s
.bind(("localhost", port
))
1000 s
.connect((address
, port
))
1002 except socket
.error
:
1003 if address
== "localhost":
1011 def find_free_port(start_port
, end_port
, address
="localhost"):
1013 Return a host free port in the range [start_port, end_port].
1015 @param start_port: First port that will be checked.
1016 @param end_port: Port immediately after the last one that will be checked.
1018 for i
in range(start_port
, end_port
):
1019 if is_port_free(i
, address
):
1024 def find_free_ports(start_port
, end_port
, count
, address
="localhost"):
1026 Return count of host free ports in the range [start_port, end_port].
1028 @count: Initial number of ports known to be free in the range.
1029 @param start_port: First port that will be checked.
1030 @param end_port: Port immediately after the last one that will be checked.
1034 while i
< end_port
and count
> 0:
1035 if is_port_free(i
, address
):
1042 # An easy way to log lines to files when the logging system can't be used
1044 _open_log_files
= {}
1045 _log_file_dir
= "/tmp"
1048 def log_line(filename
, line
):
1050 Write a line to a file. '\n' is appended to the line.
1052 @param filename: Path of file to write to, either absolute or relative to
1053 the dir set by set_log_file_dir().
1054 @param line: Line to write.
1056 global _open_log_files
, _log_file_dir
1057 if filename
not in _open_log_files
:
1058 path
= get_path(_log_file_dir
, filename
)
1060 os
.makedirs(os
.path
.dirname(path
))
1063 _open_log_files
[filename
] = open(path
, "w")
1064 timestr
= time
.strftime("%Y-%m-%d %H:%M:%S")
1065 _open_log_files
[filename
].write("%s: %s\n" % (timestr
, line
))
1066 _open_log_files
[filename
].flush()
1069 def set_log_file_dir(dir):
1071 Set the base directory for log files created by log_line().
1073 @param dir: Directory for log files.
1075 global _log_file_dir
1079 # The following are miscellaneous utility functions.
1081 def get_path(base_path
, user_path
):
1083 Translate a user specified path to a real path.
1084 If user_path is relative, append it to base_path.
1085 If user_path is absolute, return it as is.
1087 @param base_path: The base path of relative user specified paths.
1088 @param user_path: The user specified path.
1090 if os
.path
.isabs(user_path
):
1093 return os
.path
.join(base_path
, user_path
)
1096 def generate_random_string(length
):
1098 Return a random string using alphanumeric characters.
1100 @length: length of the string that will be generated.
1102 r
= random
.SystemRandom()
1104 chars
= string
.letters
+ string
.digits
1106 str += r
.choice(chars
)
1110 def generate_random_id():
1112 Return a random string suitable for use as a qemu id.
1114 return "id" + generate_random_string(6)
1117 def generate_tmp_file_name(file, ext
=None, dir='/tmp/'):
1119 Returns a temporary file name. The file is not created.
1122 file_name
= (file + '-' + time
.strftime("%Y%m%d-%H%M%S-") +
1123 generate_random_string(4))
1125 file_name
+= '.' + ext
1126 file_name
= os
.path
.join(dir, file_name
)
1127 if not os
.path
.exists(file_name
):
1133 def format_str_for_message(str):
1135 Format str so that it can be appended to a message.
1136 If str consists of one line, prefix it with a space.
1137 If str consists of multiple lines, prefix it with a newline.
1139 @param str: string that will be formatted.
1141 lines
= str.splitlines()
1142 num_lines
= len(lines
)
1143 str = "\n".join(lines
)
1146 elif num_lines
== 1:
1152 def wait_for(func
, timeout
, first
=0.0, step
=1.0, text
=None):
1154 If func() evaluates to True before timeout expires, return the
1155 value of func(). Otherwise return None.
1157 @brief: Wait until func() evaluates to True.
1159 @param timeout: Timeout in seconds
1160 @param first: Time to sleep before first attempt
1161 @param steps: Time to sleep between attempts in seconds
1162 @param text: Text to print while waiting, for debug purposes
1164 start_time
= time
.time()
1165 end_time
= time
.time() + timeout
1169 while time
.time() < end_time
:
1171 logging
.debug("%s (%f secs)", text
, (time
.time() - start_time
))
1182 def get_hash_from_file(hash_path
, dvd_basename
):
1184 Get the a hash from a given DVD image from a hash file
1185 (Hash files are usually named MD5SUM or SHA1SUM and are located inside the
1186 download directories of the DVDs)
1188 @param hash_path: Local path to a hash file.
1189 @param cd_image: Basename of a CD image
1191 hash_file
= open(hash_path
, 'r')
1192 for line
in hash_file
.readlines():
1193 if dvd_basename
in line
:
1194 return line
.split()[0]
1197 def run_tests(parser
, job
):
1199 Runs the sequence of KVM tests based on the list of dictionaries
1200 generated by the configuration system, handling dependencies.
1202 @param parser: Config parser object.
1203 @param job: Autotest job object.
1205 @return: True, if all tests ran passed, False if any of them failed.
1207 for i
, d
in enumerate(parser
.get_dicts()):
1208 logging
.info("Test %4d: %s" % (i
+ 1, d
["shortname"]))
1213 for dict in parser
.get_dicts():
1214 if dict.get("skip") == "yes":
1216 dependencies_satisfied
= True
1217 for dep
in dict.get("dep"):
1218 for test_name
in status_dict
.keys():
1219 if not dep
in test_name
:
1221 # So the only really non-fatal state is WARN,
1222 # All the others make it not safe to proceed with dependency
1224 if status_dict
[test_name
] not in ['GOOD', 'WARN']:
1225 dependencies_satisfied
= False
1227 test_iterations
= int(dict.get("iterations", 1))
1228 test_tag
= dict.get("shortname")
1230 if dependencies_satisfied
:
1231 # Setting up profilers during test execution.
1232 profilers
= dict.get("profilers", "").split()
1233 for profiler
in profilers
:
1234 job
.profilers
.add(profiler
)
1235 # We need only one execution, profiled, hence we're passing
1236 # the profile_only parameter to job.run_test().
1237 profile_only
= bool(profilers
) or None
1238 current_status
= job
.run_test_detail(dict.get("vm_type"),
1241 iterations
=test_iterations
,
1242 profile_only
=profile_only
)
1243 for profiler
in profilers
:
1244 job
.profilers
.delete(profiler
)
1246 # We will force the test to fail as TestNA during preprocessing
1247 dict['dependency_failed'] = 'yes'
1248 current_status
= job
.run_test_detail(dict.get("vm_type"),
1251 iterations
=test_iterations
)
1253 if not current_status
:
1255 status_dict
[dict.get("name")] = current_status
1260 def display_attributes(instance
):
1262 Inspects a given class instance attributes and displays them, convenient
1265 logging
.debug("Attributes set:")
1266 for member
in inspect
.getmembers(instance
):
1267 name
, value
= member
1268 attribute
= getattr(instance
, name
)
1269 if not (name
.startswith("__") or callable(attribute
) or not value
):
1270 logging
.debug(" %s: %s", name
, value
)
1273 def get_full_pci_id(pci_id
):
1275 Get full PCI ID of pci_id.
1277 @param pci_id: PCI ID of a device.
1279 cmd
= "lspci -D | awk '/%s/ {print $1}'" % pci_id
1280 status
, full_id
= commands
.getstatusoutput(cmd
)
1286 def get_vendor_from_pci_id(pci_id
):
1288 Check out the device vendor ID according to pci_id.
1290 @param pci_id: PCI ID of a device.
1292 cmd
= "lspci -n | awk '/%s/ {print $3}'" % pci_id
1293 return re
.sub(":", " ", commands
.getoutput(cmd
))
1296 def get_cpu_flags():
1298 Returns a list of the CPU flags
1300 flags_re
= re
.compile(r
'^flags\s*:(.*)')
1301 for line
in open('/proc/cpuinfo').readlines():
1302 match
= flags_re
.match(line
)
1304 return match
.groups()[0].split()
1308 def get_cpu_vendor(cpu_flags
=[], verbose
=True):
1310 Returns the name of the CPU vendor, either intel, amd or unknown
1313 cpu_flags
= get_cpu_flags()
1315 if 'vmx' in cpu_flags
:
1317 elif 'svm' in cpu_flags
:
1323 logging
.debug("Detected CPU vendor as '%s'", vendor
)
1327 def get_archive_tarball_name(source_dir
, tarball_name
, compression
):
1329 Get the name for a tarball file, based on source, name and compression
1331 if tarball_name
is None:
1332 tarball_name
= os
.path
.basename(source_dir
)
1334 if not tarball_name
.endswith('.tar'):
1335 tarball_name
= '%s.tar' % tarball_name
1337 if compression
and not tarball_name
.endswith('.%s' % compression
):
1338 tarball_name
= '%s.%s' % (tarball_name
, compression
)
1343 def archive_as_tarball(source_dir
, dest_dir
, tarball_name
=None,
1344 compression
='bz2', verbose
=True):
1346 Saves the given source directory to the given destination as a tarball
1348 If the name of the archive is omitted, it will be taken from the
1349 source_dir. If it is an absolute path, dest_dir will be ignored. But,
1350 if both the destination directory and tarball anem is given, and the
1351 latter is not an absolute path, they will be combined.
1353 For archiving directory '/tmp' in '/net/server/backup' as file
1354 'tmp.tar.bz2', simply use:
1356 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup')
1358 To save the file it with a different name, say 'host1-tmp.tar.bz2'
1359 and save it under '/net/server/backup', use:
1361 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup',
1364 To save with gzip compression instead (resulting in the file
1365 '/net/server/backup/host1-tmp.tar.gz'), use:
1367 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup',
1370 tarball_name
= get_archive_tarball_name(source_dir
,
1373 if not os
.path
.isabs(tarball_name
):
1374 tarball_path
= os
.path
.join(dest_dir
, tarball_name
)
1376 tarball_path
= tarball_name
1379 logging
.debug('Archiving %s as %s' % (source_dir
,
1382 os
.chdir(os
.path
.dirname(source_dir
))
1383 tarball
= tarfile
.TarFile(name
=tarball_path
, mode
='w')
1384 tarball
= tarball
.open(name
=tarball_path
, mode
='w:%s' % compression
)
1385 tarball
.add(os
.path
.basename(source_dir
))
1389 class Thread(threading
.Thread
):
1391 Run a function in a background thread.
1393 def __init__(self
, target
, args
=(), kwargs
={}):
1395 Initialize the instance.
1397 @param target: Function to run in the thread.
1398 @param args: Arguments to pass to target.
1399 @param kwargs: Keyword arguments to pass to target.
1401 threading
.Thread
.__init
__(self
)
1402 self
._target
= target
1404 self
._kwargs
= kwargs
1409 Run target (passed to the constructor). No point in calling this
1410 function directly. Call start() to make this function run in a new
1417 self
._retval
= self
._target
(*self
._args
, **self
._kwargs
)
1419 self
._e
= sys
.exc_info()
1422 # Avoid circular references (start() may be called only once so
1423 # it's OK to delete these)
1424 del self
._target
, self
._args
, self
._kwargs
1427 def join(self
, timeout
=None, suppress_exception
=False):
1429 Join the thread. If target raised an exception, re-raise it.
1430 Otherwise, return the value returned by target.
1432 @param timeout: Timeout value to pass to threading.Thread.join().
1433 @param suppress_exception: If True, don't re-raise the exception.
1435 threading
.Thread
.join(self
, timeout
)
1438 if not suppress_exception
:
1439 # Because the exception was raised in another thread, we
1440 # need to explicitly insert the current context into it
1441 s
= error
.exception_context(self
._e
[1])
1442 s
= error
.join_contexts(error
.get_context(), s
)
1443 error
.set_exception_context(self
._e
[1], s
)
1444 raise self
._e
[0], self
._e
[1], self
._e
[2]
1448 # Avoid circular references (join() may be called multiple times
1449 # so we can't delete these)
1454 def parallel(targets
):
1456 Run multiple functions in parallel.
1458 @param targets: A sequence of tuples or functions. If it's a sequence of
1459 tuples, each tuple will be interpreted as (target, args, kwargs) or
1460 (target, args) or (target,) depending on its length. If it's a
1461 sequence of functions, the functions will be called without
1463 @return: A list of the values returned by the functions called.
1466 for target
in targets
:
1467 if isinstance(target
, tuple) or isinstance(target
, list):
1473 return [t
.join() for t
in threads
]
1476 class VirtLoggingConfig(logging_config
.LoggingConfig
):
1478 Used with the sole purpose of providing convenient logging setup
1479 for the KVM test auxiliary programs.
1481 def configure_logging(self
, results_dir
=None, verbose
=False):
1482 super(VirtLoggingConfig
, self
).configure_logging(use_console
=True,
1486 class PciAssignable(object):
1488 Request PCI assignable devices on host. It will check whether to request
1489 PF (physical Functions) or VF (Virtual Functions).
1491 def __init__(self
, type="vf", driver
=None, driver_option
=None,
1492 names
=None, devices_requested
=None):
1494 Initialize parameter 'type' which could be:
1495 vf: Virtual Functions
1496 pf: Physical Function (actual hardware)
1497 mixed: Both includes VFs and PFs
1499 If pass through Physical NIC cards, we need to specify which devices
1500 to be assigned, e.g. 'eth1 eth2'.
1502 If pass through Virtual Functions, we need to specify how many vfs
1503 are going to be assigned, e.g. passthrough_count = 8 and max_vfs in
1506 @param type: PCI device type.
1507 @param driver: Kernel module for the PCI assignable device.
1508 @param driver_option: Module option to specify the maximum number of
1509 VFs (eg 'max_vfs=7')
1510 @param names: Physical NIC cards correspondent network interfaces,
1512 @param devices_requested: Number of devices being requested.
1515 self
.driver
= driver
1516 self
.driver_option
= driver_option
1518 self
.name_list
= names
.split()
1519 if devices_requested
:
1520 self
.devices_requested
= int(devices_requested
)
1522 self
.devices_requested
= None
1525 def _get_pf_pci_id(self
, name
, search_str
):
1527 Get the PF PCI ID according to name.
1529 @param name: Name of the PCI device.
1530 @param search_str: Search string to be used on lspci.
1532 cmd
= "ethtool -i %s | awk '/bus-info/ {print $2}'" % name
1533 s
, pci_id
= commands
.getstatusoutput(cmd
)
1534 if not (s
or "Cannot get driver information" in pci_id
):
1536 cmd
= "lspci | awk '/%s/ {print $1}'" % search_str
1537 pci_ids
= [id for id in commands
.getoutput(cmd
).splitlines()]
1538 nic_id
= int(re
.search('[0-9]+', name
).group(0))
1539 if (len(pci_ids
) - 1) < nic_id
:
1541 return pci_ids
[nic_id
]
1544 def _release_dev(self
, pci_id
):
1546 Release a single PCI device.
1548 @param pci_id: PCI ID of a given PCI device.
1550 base_dir
= "/sys/bus/pci"
1551 full_id
= get_full_pci_id(pci_id
)
1552 vendor_id
= get_vendor_from_pci_id(pci_id
)
1553 drv_path
= os
.path
.join(base_dir
, "devices/%s/driver" % full_id
)
1554 if 'pci-stub' in os
.readlink(drv_path
):
1555 cmd
= "echo '%s' > %s/new_id" % (vendor_id
, drv_path
)
1559 stub_path
= os
.path
.join(base_dir
, "drivers/pci-stub")
1560 cmd
= "echo '%s' > %s/unbind" % (full_id
, stub_path
)
1564 driver
= self
.dev_drivers
[pci_id
]
1565 cmd
= "echo '%s' > %s/bind" % (full_id
, driver
)
1572 def get_vf_devs(self
):
1574 Catch all VFs PCI IDs.
1576 @return: List with all PCI IDs for the Virtual Functions avaliable
1578 if not self
.sr_iov_setup():
1581 cmd
= "lspci | awk '/Virtual Function/ {print $1}'"
1582 return commands
.getoutput(cmd
).split()
1585 def get_pf_devs(self
):
1587 Catch all PFs PCI IDs.
1589 @return: List with all PCI IDs for the physical hardware requested
1592 for name
in self
.name_list
:
1593 pf_id
= self
._get
_pf
_pci
_id
(name
, "Ethernet")
1596 pf_ids
.append(pf_id
)
1600 def get_devs(self
, count
):
1602 Check out all devices' PCI IDs according to their name.
1604 @param count: count number of PCI devices needed for pass through
1605 @return: a list of all devices' PCI IDs
1607 if self
.type == "vf":
1608 vf_ids
= self
.get_vf_devs()
1609 elif self
.type == "pf":
1610 vf_ids
= self
.get_pf_devs()
1611 elif self
.type == "mixed":
1612 vf_ids
= self
.get_vf_devs()
1613 vf_ids
.extend(self
.get_pf_devs())
1614 return vf_ids
[0:count
]
1617 def get_vfs_count(self
):
1619 Get VFs count number according to lspci.
1621 # FIXME: Need to think out a method of identify which
1622 # 'virtual function' belongs to which physical card considering
1623 # that if the host has more than one 82576 card. PCI_ID?
1624 cmd
= "lspci | grep 'Virtual Function' | wc -l"
1625 return int(commands
.getoutput(cmd
))
1628 def check_vfs_count(self
):
1630 Check VFs count number according to the parameter driver_options.
1632 # Network card 82576 has two network interfaces and each can be
1633 # virtualized up to 7 virtual functions, therefore we multiply
1634 # two for the value of driver_option 'max_vfs'.
1635 expected_count
= int((re
.findall("(\d)", self
.driver_option
)[0])) * 2
1636 return (self
.get_vfs_count
== expected_count
)
1639 def is_binded_to_stub(self
, full_id
):
1641 Verify whether the device with full_id is already binded to pci-stub.
1643 @param full_id: Full ID for the given PCI device
1645 base_dir
= "/sys/bus/pci"
1646 stub_path
= os
.path
.join(base_dir
, "drivers/pci-stub")
1647 if os
.path
.exists(os
.path
.join(stub_path
, full_id
)):
1652 def sr_iov_setup(self
):
1654 Ensure the PCI device is working in sr_iov mode.
1656 Check if the PCI hardware device drive is loaded with the appropriate,
1657 parameters (number of VFs), and if it's not, perform setup.
1659 @return: True, if the setup was completed successfuly, False otherwise.
1662 s
, o
= commands
.getstatusoutput('lsmod | grep %s' % self
.driver
)
1665 elif not self
.check_vfs_count():
1666 os
.system("modprobe -r %s" % self
.driver
)
1671 # Re-probe driver with proper number of VFs
1673 cmd
= "modprobe %s %s" % (self
.driver
, self
.driver_option
)
1674 logging
.info("Loading the driver '%s' with option '%s'",
1675 self
.driver
, self
.driver_option
)
1676 s
, o
= commands
.getstatusoutput(cmd
)
1682 def request_devs(self
):
1684 Implement setup process: unbind the PCI device and then bind it
1685 to the pci-stub driver.
1687 @return: a list of successfully requested devices' PCI IDs.
1689 base_dir
= "/sys/bus/pci"
1690 stub_path
= os
.path
.join(base_dir
, "drivers/pci-stub")
1692 self
.pci_ids
= self
.get_devs(self
.devices_requested
)
1693 logging
.debug("The following pci_ids were found: %s", self
.pci_ids
)
1694 requested_pci_ids
= []
1695 self
.dev_drivers
= {}
1697 # Setup all devices specified for assignment to guest
1698 for pci_id
in self
.pci_ids
:
1699 full_id
= get_full_pci_id(pci_id
)
1702 drv_path
= os
.path
.join(base_dir
, "devices/%s/driver" % full_id
)
1703 dev_prev_driver
= os
.path
.realpath(os
.path
.join(drv_path
,
1704 os
.readlink(drv_path
)))
1705 self
.dev_drivers
[pci_id
] = dev_prev_driver
1707 # Judge whether the device driver has been binded to stub
1708 if not self
.is_binded_to_stub(full_id
):
1709 logging
.debug("Binding device %s to stub", full_id
)
1710 vendor_id
= get_vendor_from_pci_id(pci_id
)
1711 stub_new_id
= os
.path
.join(stub_path
, 'new_id')
1712 unbind_dev
= os
.path
.join(drv_path
, 'unbind')
1713 stub_bind
= os
.path
.join(stub_path
, 'bind')
1715 info_write_to_files
= [(vendor_id
, stub_new_id
),
1716 (full_id
, unbind_dev
),
1717 (full_id
, stub_bind
)]
1719 for content
, file in info_write_to_files
:
1721 utils
.open_write_close(file, content
)
1723 logging
.debug("Failed to write %s to file %s", content
,
1727 if not self
.is_binded_to_stub(full_id
):
1728 logging
.error("Binding device %s to stub failed", pci_id
)
1731 logging
.debug("Device %s already binded to stub", pci_id
)
1732 requested_pci_ids
.append(pci_id
)
1733 self
.pci_ids
= requested_pci_ids
1737 def release_devs(self
):
1739 Release all PCI devices currently assigned to VMs back to the
1740 virtualization host.
1743 for pci_id
in self
.dev_drivers
:
1744 if not self
._release
_dev
(pci_id
):
1745 logging
.error("Failed to release device %s to host", pci_id
)
1747 logging
.info("Released device %s successfully", pci_id
)
1752 class KojiClient(object):
1754 Stablishes a connection with the build system, either koji or brew.
1756 This class provides convenience methods to retrieve information on packages
1757 and the packages themselves hosted on the build system. Packages should be
1758 specified in the KojiPgkSpec syntax.
1761 CMD_LOOKUP_ORDER
= ['/usr/bin/brew', '/usr/bin/koji' ]
1763 CONFIG_MAP
= {'/usr/bin/brew': '/etc/brewkoji.conf',
1764 '/usr/bin/koji': '/etc/koji.conf'}
1767 def __init__(self
, cmd
=None):
1769 Verifies whether the system has koji or brew installed, then loads
1770 the configuration file that will be used to download the files.
1773 @param cmd: Optional command name, either 'brew' or 'koji'. If not
1774 set, get_default_command() is used and to look for
1778 if not KOJI_INSTALLED
:
1779 raise ValueError('No koji/brew installed on the machine')
1781 # Instance variables used by many methods
1784 self
.config_options
= {}
1787 # Set koji command or get default
1789 self
.command
= self
.get_default_command()
1793 # Check koji command
1794 if not self
.is_command_valid():
1795 raise ValueError('Koji command "%s" is not valid' % self
.command
)
1797 # Assuming command is valid, set configuration file and read it
1798 self
.config
= self
.CONFIG_MAP
[self
.command
]
1801 # Setup koji session
1802 server_url
= self
.config_options
['server']
1803 session_options
= self
.get_session_options()
1804 self
.session
= koji
.ClientSession(server_url
,
1808 def read_config(self
, check_is_valid
=True):
1810 Reads options from the Koji configuration file
1812 By default it checks if the koji configuration is valid
1814 @type check_valid: boolean
1815 @param check_valid: whether to include a check on the configuration
1820 if not self
.is_config_valid():
1821 raise ValueError('Koji config "%s" is not valid' % self
.config
)
1823 config
= ConfigParser
.ConfigParser()
1824 config
.read(self
.config
)
1826 basename
= os
.path
.basename(self
.command
)
1827 for name
, value
in config
.items(basename
):
1828 self
.config_options
[name
] = value
1831 def get_session_options(self
):
1833 Filter only options necessary for setting up a cobbler client session
1835 @returns: only the options used for session setup
1837 session_options
= {}
1838 for name
, value
in self
.config_options
.items():
1839 if name
in ('user', 'password', 'debug_xmlrpc', 'debug'):
1840 session_options
[name
] = value
1841 return session_options
1844 def is_command_valid(self
):
1846 Checks if the currently set koji command is valid
1848 @returns: True or False
1850 koji_command_ok
= True
1852 if not os
.path
.isfile(self
.command
):
1853 logging
.error('Koji command "%s" is not a regular file',
1855 koji_command_ok
= False
1857 if not os
.access(self
.command
, os
.X_OK
):
1858 logging
.warn('Koji command "%s" is not executable: this is '
1859 'not fatal but indicates an unexpected situation',
1862 if not self
.command
in self
.CONFIG_MAP
.keys():
1863 logging
.error('Koji command "%s" does not have a configuration '
1864 'file associated to it', self
.command
)
1865 koji_command_ok
= False
1867 return koji_command_ok
1870 def is_config_valid(self
):
1872 Checks if the currently set koji configuration is valid
1874 @returns: True or False
1876 koji_config_ok
= True
1878 if not os
.path
.isfile(self
.config
):
1879 logging
.error('Koji config "%s" is not a regular file', self
.config
)
1880 koji_config_ok
= False
1882 if not os
.access(self
.config
, os
.R_OK
):
1883 logging
.error('Koji config "%s" is not readable', self
.config
)
1884 koji_config_ok
= False
1886 config
= ConfigParser
.ConfigParser()
1887 config
.read(self
.config
)
1888 basename
= os
.path
.basename(self
.command
)
1889 if not config
.has_section(basename
):
1890 logging
.error('Koji configuration file "%s" does not have a '
1891 'section "%s", named after the base name of the '
1892 'currently set koji command "%s"', self
.config
,
1893 basename
, self
.command
)
1894 koji_config_ok
= False
1896 return koji_config_ok
1899 def get_default_command(self
):
1901 Looks up for koji or brew "binaries" on the system
1903 Systems with plain koji usually don't have a brew cmd, while systems
1904 with koji, have *both* koji and brew utilities. So we look for brew
1905 first, and if found, we consider that the system is configured for
1906 brew. If not, we consider this is a system with plain koji.
1908 @returns: either koji or brew command line executable path, or None
1911 for command
in self
.CMD_LOOKUP_ORDER
:
1912 if os
.path
.isfile(command
):
1913 koji_command
= command
1916 koji_command_basename
= os
.path
.basename(koji_command
)
1918 koji_command
= os_dep
.command(koji_command_basename
)
1925 def get_pkg_info(self
, pkg
):
1927 Returns information from Koji on the package
1929 @type pkg: KojiPkgSpec
1930 @param pkg: information about the package, as a KojiPkgSpec instance
1932 @returns: information from Koji about the specified package
1935 if pkg
.build
is not None:
1936 info
= self
.session
.getBuild(int(pkg
.build
))
1937 elif pkg
.tag
is not None and pkg
.package
is not None:
1938 builds
= self
.session
.listTagged(pkg
.tag
,
1941 package
=pkg
.package
)
1947 def is_pkg_valid(self
, pkg
):
1949 Checks if this package is altogether valid on Koji
1951 This verifies if the build or tag specified in the package
1952 specification actually exist on the Koji server
1954 @returns: True or False
1958 if not self
.is_pkg_spec_build_valid(pkg
):
1961 if not self
.is_pkg_spec_tag_valid(pkg
):
1968 def is_pkg_spec_build_valid(self
, pkg
):
1970 Checks if build is valid on Koji
1972 @param pkg: a Pkg instance
1974 if pkg
.build
is not None:
1975 info
= self
.session
.getBuild(int(pkg
.build
))
1981 def is_pkg_spec_tag_valid(self
, pkg
):
1983 Checks if tag is valid on Koji
1985 @type pkg: KojiPkgSpec
1986 @param pkg: a package specification
1988 if pkg
.tag
is not None:
1989 tag
= self
.session
.getTag(pkg
.tag
)
1995 def get_pkg_rpm_info(self
, pkg
, arch
=None):
1997 Returns a list of infomation on the RPM packages found on koji
1999 @type pkg: KojiPkgSpec
2000 @param pkg: a package specification
2002 @param arch: packages built for this architecture, but also including
2003 architecture independent (noarch) packages
2006 arch
= utils
.get_arch()
2008 info
= self
.get_pkg_info(pkg
)
2010 rpms
= self
.session
.listRPMs(buildID
=info
['id'],
2011 arches
=[arch
, 'noarch'])
2013 rpms
= [d
for d
in rpms
if d
['name'] in pkg
.subpackages
]
2017 def get_pkg_rpm_names(self
, pkg
, arch
=None):
2019 Gets the names for the RPM packages specified in pkg
2021 @type pkg: KojiPkgSpec
2022 @param pkg: a package specification
2024 @param arch: packages built for this architecture, but also including
2025 architecture independent (noarch) packages
2028 arch
= utils
.get_arch()
2029 rpms
= self
.get_pkg_rpm_info(pkg
, arch
)
2030 return [rpm
['name'] for rpm
in rpms
]
2033 def get_pkg_rpm_file_names(self
, pkg
, arch
=None):
2035 Gets the file names for the RPM packages specified in pkg
2037 @type pkg: KojiPkgSpec
2038 @param pkg: a package specification
2040 @param arch: packages built for this architecture, but also including
2041 architecture independent (noarch) packages
2044 arch
= utils
.get_arch()
2046 rpms
= self
.get_pkg_rpm_info(pkg
, arch
)
2048 arch_rpm_name
= koji
.pathinfo
.rpm(rpm
)
2049 rpm_name
= os
.path
.basename(arch_rpm_name
)
2050 rpm_names
.append(rpm_name
)
2054 def get_pkg_urls(self
, pkg
, arch
=None):
2056 Gets the urls for the packages specified in pkg
2058 @type pkg: KojiPkgSpec
2059 @param pkg: a package specification
2061 @param arch: packages built for this architecture, but also including
2062 architecture independent (noarch) packages
2064 info
= self
.get_pkg_info(pkg
)
2065 rpms
= self
.get_pkg_rpm_info(pkg
, arch
)
2068 rpm_name
= koji
.pathinfo
.rpm(rpm
)
2069 url
= ("%s/%s/%s/%s/%s" % (self
.config_options
['pkgurl'],
2070 info
['package_name'],
2071 info
['version'], info
['release'],
2073 rpm_urls
.append(url
)
2077 def get_pkgs(self
, pkg
, dst_dir
, arch
=None):
2079 Download the packages
2081 @type pkg: KojiPkgSpec
2082 @param pkg: a package specification
2083 @type dst_dir: string
2084 @param dst_dir: the destination directory, where the downloaded
2085 packages will be saved on
2087 @param arch: packages built for this architecture, but also including
2088 architecture independent (noarch) packages
2090 rpm_urls
= self
.get_pkg_urls(pkg
, arch
)
2091 for url
in rpm_urls
:
2093 os
.path
.join(dst_dir
, os
.path
.basename(url
)))
2096 DEFAULT_KOJI_TAG
= None
2097 def set_default_koji_tag(tag
):
2099 Sets the default tag that will be used
2101 global DEFAULT_KOJI_TAG
2102 DEFAULT_KOJI_TAG
= tag
2105 def get_default_koji_tag():
2106 return DEFAULT_KOJI_TAG
2111 A package specification syntax parser for Koji
2113 This holds information on either tag or build, and packages to be fetched
2114 from koji and possibly installed (features external do this class).
2116 New objects can be created either by providing information in the textual
2117 format or by using the actual parameters for tag, build, package and sub-
2118 packages. The textual format is useful for command line interfaces and
2119 configuration files, while using parameters is better for using this in
2120 a programatic fashion.
2122 The following sets of examples are interchangeable. Specifying all packages
2123 part of build number 1000:
2125 >>> from kvm_utils import KojiPkgSpec
2126 >>> pkg = KojiPkgSpec('1000')
2128 >>> pkg = KojiPkgSpec(build=1000)
2130 Specifying only a subset of packages of build number 1000:
2132 >>> pkg = KojiPkgSpec('1000:kernel,kernel-devel')
2134 >>> pkg = KojiPkgSpec(build=1000,
2135 subpackages=['kernel', 'kernel-devel'])
2137 Specifying the latest build for the 'kernel' package tagged with 'dist-f14':
2139 >>> pkg = KojiPkgSpec('dist-f14:kernel')
2141 >>> pkg = KojiPkgSpec(tag='dist-f14', package='kernel')
2143 Specifying the 'kernel' package using the default tag:
2145 >>> kvm_utils.set_default_koji_tag('dist-f14')
2146 >>> pkg = KojiPkgSpec('kernel')
2148 >>> pkg = KojiPkgSpec(package='kernel')
2150 Specifying the 'kernel' package using the default tag:
2152 >>> kvm_utils.set_default_koji_tag('dist-f14')
2153 >>> pkg = KojiPkgSpec('kernel')
2155 >>> pkg = KojiPkgSpec(package='kernel')
2157 If you do not specify a default tag, and give a package name without an
2158 explicit tag, your package specification is considered invalid:
2160 >>> print kvm_utils.get_default_koji_tag()
2162 >>> print kvm_utils.KojiPkgSpec('kernel').is_valid()
2165 >>> print kvm_utils.KojiPkgSpec(package='kernel').is_valid()
2171 def __init__(self
, text
='', tag
=None, build
=None,
2172 package
=None, subpackages
=[]):
2174 Instantiates a new KojiPkgSpec object
2177 @param text: a textual representation of a package on Koji that
2180 @param tag: a koji tag, example: Fedora-14-RELEASE
2181 (see U{http://fedoraproject.org/wiki/Koji#Tags_and_Targets})
2183 @param build: a koji build, example: 1001
2184 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture})
2185 @type package: string
2186 @param package: a koji package, example: python
2187 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture})
2188 @type subpackages: list of strings
2189 @param subpackages: a list of package names, usually a subset of
2190 the RPM packages generated by a given build
2193 # Set to None to indicate 'not set' (and be able to use 'is')
2197 self
.subpackages
= []
2199 self
.default_tag
= None
2201 # Textual representation takes precedence (most common use case)
2207 self
.package
= package
2208 self
.subpackages
= subpackages
2210 # Set the default tag, if set, as a fallback
2211 if not self
.build
and not self
.tag
:
2212 default_tag
= get_default_koji_tag()
2213 if default_tag
is not None:
2214 self
.tag
= default_tag
2217 def parse(self
, text
):
2219 Parses a textual representation of a package specification
2222 @param text: textual representation of a package in koji
2224 parts
= text
.count(self
.SEP
) + 1
2231 part1
, part2
= text
.split(self
.SEP
)
2234 self
.subpackages
= part2
.split(',')
2237 self
.package
= part2
2239 # Instead of erroring on more arguments, we simply ignore them
2240 # This makes the parser suitable for future syntax additions, such
2241 # as specifying the package architecture
2242 part1
, part2
, part3
= text
.split(self
.SEP
)[0:3]
2244 self
.package
= part2
2245 self
.subpackages
= part3
.split(',')
2248 def _is_invalid_neither_tag_or_build(self
):
2250 Checks if this package is invalid due to not having either a valid
2251 tag or build set, that is, both are empty.
2253 @returns: True if this is invalid and False if it's valid
2255 return (self
.tag
is None and self
.build
is None)
2258 def _is_invalid_package_but_no_tag(self
):
2260 Checks if this package is invalid due to having a package name set
2261 but tag or build set, that is, both are empty.
2263 @returns: True if this is invalid and False if it's valid
2265 return (self
.package
and not self
.tag
)
2268 def _is_invalid_subpackages_but_no_main_package(self
):
2270 Checks if this package is invalid due to having a tag set (this is Ok)
2271 but specifying subpackage names without specifying the main package
2274 Specifying subpackages without a main package name is only valid when
2275 a build is used instead of a tag.
2277 @returns: True if this is invalid and False if it's valid
2279 return (self
.tag
and self
.subpackages
and not self
.package
)
2284 Checks if this package specification is valid.
2286 Being valid means that it has enough and not conflicting information.
2287 It does not validate that the packages specified actually existe on
2290 @returns: True or False
2292 if self
._is
_invalid
_neither
_tag
_or
_build
():
2294 elif self
._is
_invalid
_package
_but
_no
_tag
():
2296 elif self
._is
_invalid
_subpackages
_but
_no
_main
_package
():
2302 def describe_invalid(self
):
2304 Describes why this is not valid, in a human friendly way
2306 if self
._is
_invalid
_neither
_tag
_or
_build
():
2307 return 'neither a tag or build are set, and of them should be set'
2308 elif self
._is
_invalid
_package
_but
_no
_tag
():
2309 return 'package name specified but no tag is set'
2310 elif self
._is
_invalid
_subpackages
_but
_no
_main
_package
():
2311 return 'subpackages specified but no main package is set'
2313 return 'unkwown reason, seems to be valid'
2318 Describe this package specification, in a human friendly way
2320 @returns: package specification description
2324 if not self
.subpackages
:
2325 description
+= 'all subpackages from %s ' % self
.package
2327 description
+= ('only subpackage(s) %s from package %s ' %
2328 (', '.join(self
.subpackages
), self
.package
))
2331 description
+= 'from build %s' % self
.build
2333 description
+= 'tagged with %s' % self
.tag
2335 raise ValueError, 'neither build or tag is set'
2339 return ('Invalid package specification: %s' %
2340 self
.describe_invalid())
2344 return ("<KojiPkgSpec tag=%s build=%s pkg=%s subpkgs=%s>" %
2345 (self
.tag
, self
.build
, self
.package
,
2346 ", ".join(self
.subpackages
)))
2349 def umount(src
, mount_point
, type):
2351 Umount the src mounted in mount_point.
2354 @mount_point: mount point
2355 @type: file system type
2358 mount_string
= "%s %s %s" % (src
, mount_point
, type)
2359 if mount_string
in file("/etc/mtab").read():
2360 umount_cmd
= "umount %s" % mount_point
2362 utils
.system(umount_cmd
)
2364 except error
.CmdError
:
2367 logging
.debug("%s is not mounted under %s", src
, mount_point
)
2371 def mount(src
, mount_point
, type, perm
="rw"):
2373 Mount the src into mount_point of the host.
2376 @mount_point: mount point
2377 @type: file system type
2378 @perm: mount premission
2380 umount(src
, mount_point
, type)
2381 mount_string
= "%s %s %s %s" % (src
, mount_point
, type, perm
)
2383 if mount_string
in file("/etc/mtab").read():
2384 logging
.debug("%s is already mounted in %s with %s",
2385 src
, mount_point
, perm
)
2388 mount_cmd
= "mount -t %s %s %s -o %s" % (type, src
, mount_point
, perm
)
2390 utils
.system(mount_cmd
)
2391 except error
.CmdError
:
2394 logging
.debug("Verify the mount through /etc/mtab")
2395 if mount_string
in file("/etc/mtab").read():
2396 logging
.debug("%s is successfully mounted", src
)
2399 logging
.error("Can't find mounted NFS share - /etc/mtab contents \n%s",
2400 file("/etc/mtab").read())
2404 def install_host_kernel(job
, params
):
2406 Install a host kernel, given the appropriate params.
2408 @param job: Job object.
2409 @param params: Dict with host kernel install params.
2411 install_type
= params
.get('host_kernel_install_type')
2413 rpm_url
= params
.get('host_kernel_rpm_url')
2415 koji_cmd
= params
.get('host_kernel_koji_cmd')
2416 koji_build
= params
.get('host_kernel_koji_build')
2417 koji_tag
= params
.get('host_kernel_koji_tag')
2419 git_repo
= params
.get('host_kernel_git_repo')
2420 git_branch
= params
.get('host_kernel_git_branch')
2421 git_commit
= params
.get('host_kernel_git_commit')
2422 patch_list
= params
.get('host_kernel_patch_list')
2424 patch_list
= patch_list
.split()
2425 kernel_config
= params
.get('host_kernel_config')
2427 if install_type
== 'rpm':
2428 logging
.info('Installing host kernel through rpm')
2429 dst
= os
.path
.join("/tmp", os
.path
.basename(rpm_url
))
2430 k
= utils
.get_file(rpm_url
, dst
)
2431 host_kernel
= job
.kernel(k
)
2432 host_kernel
.install(install_vmlinux
=False)
2435 elif install_type
in ['koji', 'brew']:
2436 k_deps
= KojiPkgSpec(tag
=koji_tag
, package
='kernel',
2437 subpackages
=['kernel-devel', 'kernel-firmware'])
2438 k
= KojiPkgSpec(tag
=koji_tag
, package
='kernel',
2439 subpackages
=['kernel'])
2441 c
= KojiClient(koji_cmd
)
2442 logging
.info('Fetching kernel dependencies (-devel, -firmware)')
2443 c
.get_pkgs(k_deps
, job
.tmpdir
)
2444 logging
.info('Installing kernel dependencies (-devel, -firmware) '
2445 'through %s', install_type
)
2446 k_deps_rpm_file_names
= [os
.path
.join(job
.tmpdir
, rpm_file_name
) for
2447 rpm_file_name
in c
.get_pkg_rpm_file_names(k_deps
)]
2448 utils
.run('rpm -U --force %s' % " ".join(k_deps_rpm_file_names
))
2450 c
.get_pkgs(k
, job
.tmpdir
)
2451 k_rpm
= os
.path
.join(job
.tmpdir
,
2452 c
.get_pkg_rpm_file_names(k
)[0])
2453 host_kernel
= job
.kernel(k_rpm
)
2454 host_kernel
.install(install_vmlinux
=False)
2457 elif install_type
== 'git':
2458 logging
.info('Chose to install host kernel through git, proceeding')
2459 repodir
= os
.path
.join("/tmp", 'kernel_src')
2460 r
= get_git_branch(git_repo
, git_branch
, repodir
, git_commit
)
2461 host_kernel
= job
.kernel(r
)
2463 host_kernel
.patch(patch_list
)
2464 host_kernel
.config(kernel_config
)
2466 host_kernel
.install()
2470 logging
.info('Chose %s, using the current kernel for the host',
2474 def if_nametoindex(ifname
):
2476 Map an interface name into its corresponding index.
2477 Returns 0 on error, as 0 is not a valid index
2479 @param ifname: interface name
2482 ctrl_sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
, 0)
2483 ifr
= struct
.pack("16si", ifname
, 0)
2484 r
= fcntl
.ioctl(ctrl_sock
, SIOCGIFINDEX
, ifr
)
2485 index
= struct
.unpack("16si", r
)[1]
2490 def vnet_hdr_probe(tapfd
):
2492 Check if the IFF_VNET_HDR is support by tun.
2494 @param tapfd: the file descriptor of /dev/net/tun
2496 u
= struct
.pack("I", 0)
2498 r
= fcntl
.ioctl(tapfd
, TUNGETFEATURES
, u
)
2499 except OverflowError:
2501 flags
= struct
.unpack("I", r
)[0]
2502 if flags
& IFF_VNET_HDR
:
2508 def open_tap(devname
, ifname
, vnet_hdr
=True):
2510 Open a tap device and returns its file descriptor which is used by
2511 fd=<fd> parameter of qemu-kvm.
2513 @param ifname: TAP interface name
2514 @param vnet_hdr: Whether enable the vnet header
2517 tapfd
= os
.open(devname
, os
.O_RDWR
)
2519 raise TAPModuleError(devname
, "open", e
)
2520 flags
= IFF_TAP | IFF_NO_PI
2521 if vnet_hdr
and vnet_hdr_probe(tapfd
):
2522 flags |
= IFF_VNET_HDR
2524 ifr
= struct
.pack("16sh", ifname
, flags
)
2526 r
= fcntl
.ioctl(tapfd
, TUNSETIFF
, ifr
)
2527 except IOError, details
:
2528 raise TAPCreationError(ifname
, details
)
2529 ifname
= struct
.unpack("16sh", r
)[0].strip("\x00")
2533 def add_to_bridge(ifname
, brname
):
2535 Add a TAP device to bridge
2537 @param ifname: Name of TAP device
2538 @param brname: Name of the bridge
2540 ctrl_sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
, 0)
2541 index
= if_nametoindex(ifname
)
2543 raise TAPNotExistError(ifname
)
2544 ifr
= struct
.pack("16si", brname
, index
)
2546 r
= fcntl
.ioctl(ctrl_sock
, SIOCBRADDIF
, ifr
)
2547 except IOError, details
:
2548 raise BRAddIfError(ifname
, brname
, details
)
2552 def bring_up_ifname(ifname
):
2554 Bring up an interface
2556 @param ifname: Name of the interface
2558 ctrl_sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
, 0)
2559 ifr
= struct
.pack("16si", ifname
, IFF_UP
)
2561 fcntl
.ioctl(ctrl_sock
, SIOCSIFFLAGS
, ifr
)
2563 raise TAPBringUpError(ifname
)
2567 def if_set_macaddress(ifname
, mac
):
2569 Set the mac address for an interface
2571 @param ifname: Name of the interface
2574 ctrl_sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
, 0)
2576 ifr
= struct
.pack("256s", ifname
)
2578 mac_dev
= fcntl
.ioctl(ctrl_sock
, SIOCGIFHWADDR
, ifr
)[18:24]
2579 mac_dev
= ":".join(["%02x" % ord(m
) for m
in mac_dev
])
2581 raise HwAddrGetError(ifname
)
2583 if mac_dev
.lower() == mac
.lower():
2586 ifr
= struct
.pack("16sH14s", ifname
, 1,
2587 "".join([chr(int(m
, 16)) for m
in mac
.split(":")]))
2589 fcntl
.ioctl(ctrl_sock
, SIOCSIFHWADDR
, ifr
)
2592 raise HwAddrSetError(ifname
, mac
)