Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(164)

Side by Side Diff: client/tests/kvm/kvm_utils.py

Issue 6883035: Merge remote branch 'autotest-upstream/master' into autotest-merge (Closed) Base URL: ssh://gitrw.chromium.org:9222/autotest.git@master
Patch Set: patch Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 """ 1 """
2 KVM test utility functions. 2 KVM test utility functions.
3 3
4 @copyright: 2008-2009 Red Hat Inc. 4 @copyright: 2008-2009 Red Hat Inc.
5 """ 5 """
6 6
7 import time, string, random, socket, os, signal, re, logging, commands, cPickle 7 import time, string, random, socket, os, signal, re, logging, commands, cPickle
8 import fcntl, shelve, ConfigParser, rss_file_transfer, threading, sys, UserDict 8 import fcntl, shelve, ConfigParser, rss_file_transfer, threading, sys, UserDict
9 import inspect
9 from autotest_lib.client.bin import utils, os_dep 10 from autotest_lib.client.bin import utils, os_dep
10 from autotest_lib.client.common_lib import error, logging_config 11 from autotest_lib.client.common_lib import error, logging_config
11 import kvm_subprocess 12 import kvm_subprocess
12 try: 13 try:
13 import koji 14 import koji
14 KOJI_INSTALLED = True 15 KOJI_INSTALLED = True
15 except ImportError: 16 except ImportError:
16 KOJI_INSTALLED = False 17 KOJI_INSTALLED = False
17 18
18 19
(...skipping 649 matching lines...) Expand 10 before | Expand all | Expand 10 after
668 return remote_login(client, host, port, username, password, prompt, 669 return remote_login(client, host, port, username, password, prompt,
669 linesep, log_filename, internal_timeout) 670 linesep, log_filename, internal_timeout)
670 except LoginError, e: 671 except LoginError, e:
671 logging.debug(e) 672 logging.debug(e)
672 time.sleep(2) 673 time.sleep(2)
673 # Timeout expired; try one more time but don't catch exceptions 674 # Timeout expired; try one more time but don't catch exceptions
674 return remote_login(client, host, port, username, password, prompt, 675 return remote_login(client, host, port, username, password, prompt,
675 linesep, log_filename, internal_timeout) 676 linesep, log_filename, internal_timeout)
676 677
677 678
678 def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): 679 def _remote_scp(session, password_list, transfer_timeout=600, login_timeout=10):
679 """ 680 """
680 Transfer file(s) to a remote host (guest) using SCP. Wait for questions 681 Transfer file(s) to a remote host (guest) using SCP. Wait for questions
681 and provide answers. If login_timeout expires while waiting for output 682 and provide answers. If login_timeout expires while waiting for output
682 from the child (e.g. a password prompt), fail. If transfer_timeout expires 683 from the child (e.g. a password prompt), fail. If transfer_timeout expires
683 while waiting for the transfer to complete, fail. 684 while waiting for the transfer to complete, fail.
684 685
685 @brief: Transfer files using SCP, given a command line. 686 @brief: Transfer files using SCP, given a command line.
686 687
687 @param session: An Expect or ShellSession instance to operate on 688 @param session: An Expect or ShellSession instance to operate on
688 @param password: The password to send in reply to a password prompt. 689 @param password_list: Password list to send in reply to the password prompt
689 @param transfer_timeout: The time duration (in seconds) to wait for the 690 @param transfer_timeout: The time duration (in seconds) to wait for the
690 transfer to complete. 691 transfer to complete.
691 @param login_timeout: The maximal time duration (in seconds) to wait for 692 @param login_timeout: The maximal time duration (in seconds) to wait for
692 each step of the login procedure (i.e. the "Are you sure" prompt or 693 each step of the login procedure (i.e. the "Are you sure" prompt or
693 the password prompt) 694 the password prompt)
694 @raise SCPAuthenticationError: If authentication fails 695 @raise SCPAuthenticationError: If authentication fails
695 @raise SCPTransferTimeoutError: If the transfer fails to complete in time 696 @raise SCPTransferTimeoutError: If the transfer fails to complete in time
696 @raise SCPTransferFailedError: If the process terminates with a nonzero 697 @raise SCPTransferFailedError: If the process terminates with a nonzero
697 exit code 698 exit code
698 @raise SCPError: If some other error occurs 699 @raise SCPError: If some other error occurs
699 """ 700 """
700 password_prompt_count = 0 701 password_prompt_count = 0
701 timeout = login_timeout 702 timeout = login_timeout
702 authentication_done = False 703 authentication_done = False
703 704
705 scp_type = len(password_list)
706
704 while True: 707 while True:
705 try: 708 try:
706 match, text = session.read_until_last_line_matches( 709 match, text = session.read_until_last_line_matches(
707 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"], 710 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
708 timeout=timeout, internal_timeout=0.5) 711 timeout=timeout, internal_timeout=0.5)
709 if match == 0: # "Are you sure you want to continue connecting" 712 if match == 0: # "Are you sure you want to continue connecting"
710 logging.debug("Got 'Are you sure...'; sending 'yes'") 713 logging.debug("Got 'Are you sure...'; sending 'yes'")
711 session.sendline("yes") 714 session.sendline("yes")
712 continue 715 continue
713 elif match == 1: # "password:" 716 elif match == 1: # "password:"
714 if password_prompt_count == 0: 717 if password_prompt_count == 0:
715 logging.debug("Got password prompt; sending '%s'", password) 718 logging.debug("Got password prompt; sending '%s'" %
716 session.sendline(password) 719 password_list[password_prompt_count])
720 session.sendline(password_list[password_prompt_count])
721 password_prompt_count += 1
722 timeout = transfer_timeout
723 if scp_type == 1:
724 authentication_done = True
725 continue
726 elif password_prompt_count == 1 and scp_type == 2:
727 logging.debug("Got password prompt; sending '%s'" %
728 password_list[password_prompt_count])
729 session.sendline(password_list[password_prompt_count])
717 password_prompt_count += 1 730 password_prompt_count += 1
718 timeout = transfer_timeout 731 timeout = transfer_timeout
719 authentication_done = True 732 authentication_done = True
720 continue 733 continue
721 else: 734 else:
722 raise SCPAuthenticationError("Got password prompt twice", 735 raise SCPAuthenticationError("Got password prompt twice",
723 text) 736 text)
724 elif match == 2: # "lost connection" 737 elif match == 2: # "lost connection"
725 raise SCPError("SCP client said 'lost connection'", text) 738 raise SCPError("SCP client said 'lost connection'", text)
726 except kvm_subprocess.ExpectTimeoutError, e: 739 except kvm_subprocess.ExpectTimeoutError, e:
727 if authentication_done: 740 if authentication_done:
728 raise SCPTransferTimeoutError(e.output) 741 raise SCPTransferTimeoutError(e.output)
729 else: 742 else:
730 raise SCPAuthenticationTimeoutError(e.output) 743 raise SCPAuthenticationTimeoutError(e.output)
731 except kvm_subprocess.ExpectProcessTerminatedError, e: 744 except kvm_subprocess.ExpectProcessTerminatedError, e:
732 if e.status == 0: 745 if e.status == 0:
733 logging.debug("SCP process terminated with status 0") 746 logging.debug("SCP process terminated with status 0")
734 break 747 break
735 else: 748 else:
736 raise SCPTransferFailedError(e.status, e.output) 749 raise SCPTransferFailedError(e.status, e.output)
737 750
738 751
739 def remote_scp(command, password, log_filename=None, transfer_timeout=600, 752 def remote_scp(command, password_list, log_filename=None, transfer_timeout=600,
740 login_timeout=10): 753 login_timeout=10):
741 """ 754 """
742 Transfer file(s) to a remote host (guest) using SCP. 755 Transfer file(s) to a remote host (guest) using SCP.
743 756
744 @brief: Transfer files using SCP, given a command line. 757 @brief: Transfer files using SCP, given a command line.
745 758
746 @param command: The command to execute 759 @param command: The command to execute
747 (e.g. "scp -r foobar root@localhost:/tmp/"). 760 (e.g. "scp -r foobar root@localhost:/tmp/").
748 @param password: The password to send in reply to a password prompt. 761 @param password_list: Password list to send in reply to a password prompt.
749 @param log_filename: If specified, log all output to this file 762 @param log_filename: If specified, log all output to this file
750 @param transfer_timeout: The time duration (in seconds) to wait for the 763 @param transfer_timeout: The time duration (in seconds) to wait for the
751 transfer to complete. 764 transfer to complete.
752 @param login_timeout: The maximal time duration (in seconds) to wait for 765 @param login_timeout: The maximal time duration (in seconds) to wait for
753 each step of the login procedure (i.e. the "Are you sure" prompt 766 each step of the login procedure (i.e. the "Are you sure" prompt
754 or the password prompt) 767 or the password prompt)
755 @raise: Whatever _remote_scp() raises 768 @raise: Whatever _remote_scp() raises
756 """ 769 """
757 logging.debug("Trying to SCP with command '%s', timeout %ss", 770 logging.debug("Trying to SCP with command '%s', timeout %ss",
758 command, transfer_timeout) 771 command, transfer_timeout)
759 if log_filename: 772 if log_filename:
760 output_func = log_line 773 output_func = log_line
761 output_params = (log_filename,) 774 output_params = (log_filename,)
762 else: 775 else:
763 output_func = None 776 output_func = None
764 output_params = () 777 output_params = ()
765 session = kvm_subprocess.Expect(command, 778 session = kvm_subprocess.Expect(command,
766 output_func=output_func, 779 output_func=output_func,
767 output_params=output_params) 780 output_params=output_params)
768 try: 781 try:
769 _remote_scp(session, password, transfer_timeout, login_timeout) 782 _remote_scp(session, password_list, transfer_timeout, login_timeout)
770 finally: 783 finally:
771 session.close() 784 session.close()
772 785
773 786
774 def scp_to_remote(host, port, username, password, local_path, remote_path, 787 def scp_to_remote(host, port, username, password, local_path, remote_path,
775 log_filename=None, timeout=600): 788 log_filename=None, timeout=600):
776 """ 789 """
777 Copy files to a remote host (guest) through scp. 790 Copy files to a remote host (guest) through scp.
778 791
779 @param host: Hostname or IP address 792 @param host: Hostname or IP address
780 @param username: Username (if required) 793 @param username: Username (if required)
781 @param password: Password (if required) 794 @param password: Password (if required)
782 @param local_path: Path on the local machine where we are copying from 795 @param local_path: Path on the local machine where we are copying from
783 @param remote_path: Path on the remote machine where we are copying to 796 @param remote_path: Path on the remote machine where we are copying to
784 @param log_filename: If specified, log all output to this file 797 @param log_filename: If specified, log all output to this file
785 @param timeout: The time duration (in seconds) to wait for the transfer 798 @param timeout: The time duration (in seconds) to wait for the transfer
786 to complete. 799 to complete.
787 @raise: Whatever remote_scp() raises 800 @raise: Whatever remote_scp() raises
788 """ 801 """
789 command = ("scp -v -o UserKnownHostsFile=/dev/null " 802 command = ("scp -v -o UserKnownHostsFile=/dev/null "
790 "-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" % 803 "-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" %
791 (port, local_path, username, host, remote_path)) 804 (port, local_path, username, host, remote_path))
792 remote_scp(command, password, log_filename, timeout) 805 password_list = []
806 password_list.append(password)
807 return remote_scp(command, password_list, log_filename, timeout)
808
793 809
794 810
795 def scp_from_remote(host, port, username, password, remote_path, local_path, 811 def scp_from_remote(host, port, username, password, remote_path, local_path,
796 log_filename=None, timeout=600): 812 log_filename=None, timeout=600):
797 """ 813 """
798 Copy files from a remote host (guest). 814 Copy files from a remote host (guest).
799 815
800 @param host: Hostname or IP address 816 @param host: Hostname or IP address
801 @param username: Username (if required) 817 @param username: Username (if required)
802 @param password: Password (if required) 818 @param password: Password (if required)
803 @param local_path: Path on the local machine where we are copying from 819 @param local_path: Path on the local machine where we are copying from
804 @param remote_path: Path on the remote machine where we are copying to 820 @param remote_path: Path on the remote machine where we are copying to
805 @param log_filename: If specified, log all output to this file 821 @param log_filename: If specified, log all output to this file
806 @param timeout: The time duration (in seconds) to wait for the transfer 822 @param timeout: The time duration (in seconds) to wait for the transfer
807 to complete. 823 to complete.
808 @raise: Whatever remote_scp() raises 824 @raise: Whatever remote_scp() raises
809 """ 825 """
810 command = ("scp -v -o UserKnownHostsFile=/dev/null " 826 command = ("scp -v -o UserKnownHostsFile=/dev/null "
811 "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" % 827 "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" %
812 (port, username, host, remote_path, local_path)) 828 (port, username, host, remote_path, local_path))
813 remote_scp(command, password, log_filename, timeout) 829 password_list = []
830 password_list.append(password)
831 remote_scp(command, password_list, log_filename, timeout)
832
833
834 def scp_between_remotes(src, dst, port, s_passwd, d_passwd, s_name, d_name,
835 s_path, d_path, log_filename=None, timeout=600):
836 """
837 Copy files from a remote host (guest) to another remote host (guest).
838
839 @param src/dst: Hostname or IP address of src and dst
840 @param s_name/d_name: Username (if required)
841 @param s_passwd/d_passwd: Password (if required)
842 @param s_path/d_path: Path on the remote machine where we are copying
843 from/to
844 @param log_filename: If specified, log all output to this file
845 @param timeout: The time duration (in seconds) to wait for the transfer
846 to complete.
847
848 @return: True on success and False on failure.
849 """
850 command = ("scp -v -o UserKnownHostsFile=/dev/null -o "
851 "PreferredAuthentications=password -r -P %s %s@%s:%s %s@%s:%s" %
852 (port, s_name, src, s_path, d_name, dst, d_path))
853 password_list = []
854 password_list.append(s_passwd)
855 password_list.append(d_passwd)
856 return remote_scp(command, password_list, log_filename, timeout)
814 857
815 858
816 def copy_files_to(address, client, username, password, port, local_path, 859 def copy_files_to(address, client, username, password, port, local_path,
817 remote_path, log_filename=None, verbose=False, timeout=600): 860 remote_path, log_filename=None, verbose=False, timeout=600):
818 """ 861 """
819 Copy files to a remote host (guest) using the selected client. 862 Copy files to a remote host (guest) using the selected client.
820 863
821 @param client: Type of transfer client 864 @param client: Type of transfer client
822 @param username: Username (if required) 865 @param username: Username (if required)
823 @param password: Password (if requried) 866 @param password: Password (if requried)
(...skipping 277 matching lines...) Expand 10 before | Expand all | Expand 10 after
1101 failed = False 1144 failed = False
1102 1145
1103 for dict in parser.get_dicts(): 1146 for dict in parser.get_dicts():
1104 if dict.get("skip") == "yes": 1147 if dict.get("skip") == "yes":
1105 continue 1148 continue
1106 dependencies_satisfied = True 1149 dependencies_satisfied = True
1107 for dep in dict.get("dep"): 1150 for dep in dict.get("dep"):
1108 for test_name in status_dict.keys(): 1151 for test_name in status_dict.keys():
1109 if not dep in test_name: 1152 if not dep in test_name:
1110 continue 1153 continue
1111 if not status_dict[test_name]: 1154 # So the only really non-fatal state is WARN,
1155 # All the others make it not safe to proceed with dependency
1156 # execution
1157 if status_dict[test_name] not in ['GOOD', 'WARN']:
1112 dependencies_satisfied = False 1158 dependencies_satisfied = False
1113 break 1159 break
1160 test_iterations = int(dict.get("iterations", 1))
1161 test_tag = dict.get("shortname")
1162
1114 if dependencies_satisfied: 1163 if dependencies_satisfied:
1115 test_iterations = int(dict.get("iterations", 1))
1116 test_tag = dict.get("shortname")
1117 # Setting up profilers during test execution. 1164 # Setting up profilers during test execution.
1118 profilers = dict.get("profilers", "").split() 1165 profilers = dict.get("profilers", "").split()
1119 for profiler in profilers: 1166 for profiler in profilers:
1120 job.profilers.add(profiler) 1167 job.profilers.add(profiler)
1121
1122 # We need only one execution, profiled, hence we're passing 1168 # We need only one execution, profiled, hence we're passing
1123 # the profile_only parameter to job.run_test(). 1169 # the profile_only parameter to job.run_test().
1124 current_status = job.run_test("kvm", params=dict, tag=test_tag, 1170 profile_only = bool(profilers) or None
1125 iterations=test_iterations, 1171 current_status = job.run_test_detail("kvm", params=dict,
1126 profile_only= bool(profilers) or None) 1172 tag=test_tag,
1127 1173 iterations=test_iterations,
1174 profile_only=profile_only)
1128 for profiler in profilers: 1175 for profiler in profilers:
1129 job.profilers.delete(profiler) 1176 job.profilers.delete(profiler)
1177 else:
1178 # We will force the test to fail as TestNA during preprocessing
1179 dict['dependency_failed'] = 'yes'
1180 current_status = job.run_test_detail("kvm", params=dict,
1181 tag=test_tag,
1182 iterations=test_iterations)
1130 1183
1131 if not current_status: 1184 if not current_status:
1132 failed = True 1185 failed = True
1133 else:
1134 current_status = False
1135 status_dict[dict.get("name")] = current_status 1186 status_dict[dict.get("name")] = current_status
1136 1187
1137 return not failed 1188 return not failed
1138 1189
1139 1190
1140 def create_report(report_dir, results_dir): 1191 def create_report(report_dir, results_dir):
1141 """ 1192 """
1142 Creates a neatly arranged HTML results report in the results dir. 1193 Creates a neatly arranged HTML results report in the results dir.
1143 1194
1144 @param report_dir: Directory where the report script is located. 1195 @param report_dir: Directory where the report script is located.
1145 @param results_dir: Directory where the results will be output. 1196 @param results_dir: Directory where the results will be output.
1146 """ 1197 """
1147 reporter = os.path.join(report_dir, 'html_report.py') 1198 reporter = os.path.join(report_dir, 'html_report.py')
1148 html_file = os.path.join(results_dir, 'results.html') 1199 html_file = os.path.join(results_dir, 'results.html')
1149 os.system('%s -r %s -f %s -R' % (reporter, results_dir, html_file)) 1200 os.system('%s -r %s -f %s -R' % (reporter, results_dir, html_file))
1150 1201
1151 1202
1203 def display_attributes(instance):
1204 """
1205 Inspects a given class instance attributes and displays them, convenient
1206 for debugging.
1207 """
1208 logging.debug("Attributes set:")
1209 for member in inspect.getmembers(instance):
1210 name, value = member
1211 attribute = getattr(instance, name)
1212 if not (name.startswith("__") or callable(attribute) or not value):
1213 logging.debug(" %s: %s", name, value)
1214
1215
1152 def get_full_pci_id(pci_id): 1216 def get_full_pci_id(pci_id):
1153 """ 1217 """
1154 Get full PCI ID of pci_id. 1218 Get full PCI ID of pci_id.
1155 1219
1156 @param pci_id: PCI ID of a device. 1220 @param pci_id: PCI ID of a device.
1157 """ 1221 """
1158 cmd = "lspci -D | awk '/%s/ {print $1}'" % pci_id 1222 cmd = "lspci -D | awk '/%s/ {print $1}'" % pci_id
1159 status, full_id = commands.getstatusoutput(cmd) 1223 status, full_id = commands.getstatusoutput(cmd)
1160 if status != 0: 1224 if status != 0:
1161 return None 1225 return None
(...skipping 366 matching lines...) Expand 10 before | Expand all | Expand 10 after
1528 try: 1592 try:
1529 for pci_id in self.dev_drivers: 1593 for pci_id in self.dev_drivers:
1530 if not self._release_dev(pci_id): 1594 if not self._release_dev(pci_id):
1531 logging.error("Failed to release device %s to host", pci_id) 1595 logging.error("Failed to release device %s to host", pci_id)
1532 else: 1596 else:
1533 logging.info("Released device %s successfully", pci_id) 1597 logging.info("Released device %s successfully", pci_id)
1534 except: 1598 except:
1535 return 1599 return
1536 1600
1537 1601
1538 class KojiDownloader(object): 1602 class KojiClient(object):
1539 """ 1603 """
1540 Stablish a connection with the build system, either koji or brew. 1604 Stablishes a connection with the build system, either koji or brew.
1541 1605
1542 This class provides a convenience methods to retrieve packages hosted on 1606 This class provides convenience methods to retrieve information on packages
1543 the build system. 1607 and the packages themselves hosted on the build system. Packages should be
1608 specified in the KojiPgkSpec syntax.
1544 """ 1609 """
1545 def __init__(self, cmd): 1610
1611 CMD_LOOKUP_ORDER = ['/usr/bin/brew', '/usr/bin/koji' ]
1612
1613 CONFIG_MAP = {'/usr/bin/brew': '/etc/brewkoji.conf',
1614 '/usr/bin/koji': '/etc/koji.conf'}
1615
1616
1617 def __init__(self, cmd=None):
1546 """ 1618 """
1547 Verifies whether the system has koji or brew installed, then loads 1619 Verifies whether the system has koji or brew installed, then loads
1548 the configuration file that will be used to download the files. 1620 the configuration file that will be used to download the files.
1549 1621
1550 @param cmd: Command name, either 'brew' or 'koji'. It is important 1622 @type cmd: string
1551 to figure out the appropriate configuration used by the 1623 @param cmd: Optional command name, either 'brew' or 'koji'. If not
1552 downloader. 1624 set, get_default_command() is used and to look for
1553 @param dst_dir: Destination dir for the packages. 1625 one of them.
1626 @raise: ValueError
1554 """ 1627 """
1555 if not KOJI_INSTALLED: 1628 if not KOJI_INSTALLED:
1556 raise ValueError('No koji/brew installed on the machine') 1629 raise ValueError('No koji/brew installed on the machine')
1557 1630
1558 if os.path.isfile(cmd): 1631 # Instance variables used by many methods
1559 koji_cmd = cmd 1632 self.command = None
1633 self.config = None
1634 self.config_options = {}
1635 self.session = None
1636
1637 # Set koji command or get default
1638 if cmd is None:
1639 self.command = self.get_default_command()
1560 else: 1640 else:
1561 koji_cmd = os_dep.command(cmd) 1641 self.command = cmd
1562 1642
1563 logging.debug("Found %s as the buildsystem interface", koji_cmd) 1643 # Check koji command
1564 1644 if not self.is_command_valid():
1565 config_map = {'/usr/bin/koji': '/etc/koji.conf', 1645 raise ValueError('Koji command "%s" is not valid' % self.command)
1566 '/usr/bin/brew': '/etc/brewkoji.conf'} 1646
1567 1647 # Assuming command is valid, set configuration file and read it
1568 try: 1648 self.config = self.CONFIG_MAP[self.command]
1569 config_file = config_map[koji_cmd] 1649 self.read_config()
1570 except IndexError: 1650
1571 raise ValueError('Could not find config file for %s' % koji_cmd) 1651 # Setup koji session
1572 1652 server_url = self.config_options['server']
1573 base_name = os.path.basename(koji_cmd) 1653 session_options = self.get_session_options()
1574 if os.access(config_file, os.F_OK): 1654 self.session = koji.ClientSession(server_url,
1575 f = open(config_file) 1655 session_options)
1576 config = ConfigParser.ConfigParser() 1656
1577 config.readfp(f) 1657
1578 f.close() 1658 def read_config(self, check_is_valid=True):
1579 else: 1659 '''
1580 raise IOError('Configuration file %s missing or with wrong ' 1660 Reads options from the Koji configuration file
1581 'permissions' % config_file) 1661
1582 1662 By default it checks if the koji configuration is valid
1583 if config.has_section(base_name): 1663
1584 self.koji_options = {} 1664 @type check_valid: boolean
1585 session_options = {} 1665 @param check_valid: whether to include a check on the configuration
1586 server = None 1666 @raises: ValueError
1587 for name, value in config.items(base_name): 1667 @returns: None
1588 if name in ('user', 'password', 'debug_xmlrpc', 'debug'): 1668 '''
1589 session_options[name] = value 1669 if check_is_valid:
1590 self.koji_options[name] = value 1670 if not self.is_config_valid():
1591 self.session = koji.ClientSession(self.koji_options['server'], 1671 raise ValueError('Koji config "%s" is not valid' % self.config)
1592 session_options) 1672
1593 else: 1673 config = ConfigParser.ConfigParser()
1594 raise ValueError('Koji config file %s does not have a %s ' 1674 config.read(self.config)
1595 'session' % (config_file, base_name)) 1675
1596 1676 basename = os.path.basename(self.command)
1597 1677 for name, value in config.items(basename):
1598 def get(self, src_package, dst_dir, rfilter=None, tag=None, build=None, 1678 self.config_options[name] = value
1599 arch=None): 1679
1600 """ 1680
1601 Download a list of packages from the build system. 1681 def get_session_options(self):
1602 1682 '''
1603 This will download all packages originated from source package [package] 1683 Filter only options necessary for setting up a cobbler client session
1604 with given [tag] or [build] for the architecture reported by the 1684
1605 machine. 1685 @returns: only the options used for session setup
1606 1686 '''
1607 @param src_package: Source package name. 1687 session_options = {}
1608 @param dst_dir: Destination directory for the downloaded packages. 1688 for name, value in self.config_options.items():
1609 @param rfilter: Regexp filter, only download the packages that match 1689 if name in ('user', 'password', 'debug_xmlrpc', 'debug'):
1610 that particular filter. 1690 session_options[name] = value
1611 @param tag: Build system tag. 1691 return session_options
1612 @param build: Build system ID. 1692
1613 @param arch: Package arch. Useful when you want to download noarch 1693
1614 packages. 1694 def is_command_valid(self):
1615 1695 '''
1616 @return: List of paths with the downloaded rpm packages. 1696 Checks if the currently set koji command is valid
1617 """ 1697
1618 if build and build.isdigit(): 1698 @returns: True or False
1619 build = int(build) 1699 '''
1620 1700 koji_command_ok = True
1621 if tag and build: 1701
1622 logging.info("Both tag and build parameters provided, ignoring tag " 1702 if not os.path.isfile(self.command):
1623 "parameter...") 1703 logging.error('Koji command "%s" is not a regular file',
1624 1704 self.command)
1625 if not tag and not build: 1705 koji_command_ok = False
1626 raise ValueError("Koji install selected but neither koji_tag " 1706
1627 "nor koji_build parameters provided. Please " 1707 if not os.access(self.command, os.X_OK):
1628 "provide an appropriate tag or build name.") 1708 logging.warn('Koji command "%s" is not executable: this is '
1629 1709 'not fatal but indicates an unexpected situation',
1630 if not build: 1710 self.command)
1631 builds = self.session.listTagged(tag, latest=True, inherit=True, 1711
1632 package=src_package) 1712 if not self.command in self.CONFIG_MAP.keys():
1633 if not builds: 1713 logging.error('Koji command "%s" does not have a configuration '
1634 raise ValueError("Tag %s has no builds of %s" % (tag, 1714 'file associated to it', self.command)
1635 src_package)) 1715 koji_command_ok = False
1636 info = builds[0] 1716
1637 else: 1717 return koji_command_ok
1638 info = self.session.getBuild(build) 1718
1639 1719
1640 if info is None: 1720 def is_config_valid(self):
1641 raise ValueError('No such brew/koji build: %s' % build) 1721 '''
1642 1722 Checks if the currently set koji configuration is valid
1723
1724 @returns: True or False
1725 '''
1726 koji_config_ok = True
1727
1728 if not os.path.isfile(self.config):
1729 logging.error('Koji config "%s" is not a regular file', self.config)
1730 koji_config_ok = False
1731
1732 if not os.access(self.config, os.R_OK):
1733 logging.error('Koji config "%s" is not readable', self.config)
1734 koji_config_ok = False
1735
1736 config = ConfigParser.ConfigParser()
1737 config.read(self.config)
1738 basename = os.path.basename(self.command)
1739 if not config.has_section(basename):
1740 logging.error('Koji configuration file "%s" does not have a '
1741 'section "%s", named after the base name of the '
1742 'currently set koji command "%s"', self.config,
1743 basename, self.command)
1744 koji_config_ok = False
1745
1746 return koji_config_ok
1747
1748
1749 def get_default_command(self):
1750 '''
1751 Looks up for koji or brew "binaries" on the system
1752
1753 Systems with plain koji usually don't have a brew cmd, while systems
1754 with koji, have *both* koji and brew utilities. So we look for brew
1755 first, and if found, we consider that the system is configured for
1756 brew. If not, we consider this is a system with plain koji.
1757
1758 @returns: either koji or brew command line executable path, or None
1759 '''
1760 koji_command = None
1761 for command in self.CMD_LOOKUP_ORDER:
1762 if os.path.isfile(command):
1763 koji_command = command
1764 break
1765 else:
1766 koji_command_basename = os.path.basename(koji_command)
1767 try:
1768 koji_command = os_dep.command(koji_command_basename)
1769 break
1770 except ValueError:
1771 pass
1772 return koji_command
1773
1774
1775 def get_pkg_info(self, pkg):
1776 '''
1777 Returns information from Koji on the package
1778
1779 @type pkg: KojiPkgSpec
1780 @param pkg: information about the package, as a KojiPkgSpec instance
1781
1782 @returns: information from Koji about the specified package
1783 '''
1784 info = {}
1785 if pkg.build is not None:
1786 info = self.session.getBuild(int(pkg.build))
1787 elif pkg.tag is not None and pkg.package is not None:
1788 builds = self.session.listTagged(pkg.tag,
1789 latest=True,
1790 inherit=True,
1791 package=pkg.package)
1792 if builds:
1793 info = builds[0]
1794 return info
1795
1796
1797 def is_pkg_valid(self, pkg):
1798 '''
1799 Checks if this package is altogether valid on Koji
1800
1801 This verifies if the build or tag specified in the package
1802 specification actually exist on the Koji server
1803
1804 @returns: True or False
1805 '''
1806 valid = True
1807 if not self.is_pkg_spec_build_valid(pkg):
1808 valid = False
1809 if not self.is_pkg_spec_tag_valid(pkg):
1810 valid = False
1811 return valid
1812
1813
1814 def is_pkg_spec_build_valid(self, pkg):
1815 '''
1816 Checks if build is valid on Koji
1817
1818 @param pkg: a Pkg instance
1819 '''
1820 if pkg.build is not None:
1821 info = self.session.getBuild(int(pkg.build))
1822 if info:
1823 return True
1824 return False
1825
1826
1827 def is_pkg_spec_tag_valid(self, pkg):
1828 '''
1829 Checks if tag is valid on Koji
1830
1831 @type pkg: KojiPkgSpec
1832 @param pkg: a package specification
1833 '''
1834 if pkg.tag is not None:
1835 tag = self.session.getTag(pkg.tag)
1836 if tag:
1837 return True
1838 return False
1839
1840
1841 def get_pkg_rpm_info(self, pkg, arch=None):
1842 '''
1843 Returns a list of infomation on the RPM packages found on koji
1844
1845 @type pkg: KojiPkgSpec
1846 @param pkg: a package specification
1847 @type arch: string
1848 @param arch: packages built for this architecture, but also including
1849 architecture independent (noarch) packages
1850 '''
1643 if arch is None: 1851 if arch is None:
1644 arch = utils.get_arch() 1852 arch = utils.get_arch()
1645 1853 rpms = []
1646 rpms = self.session.listRPMs(buildID=info['id'], 1854 info = self.get_pkg_info(pkg)
1647 arches=arch) 1855 if info:
1648 if not rpms: 1856 rpms = self.session.listRPMs(buildID=info['id'],
1649 raise ValueError("No %s packages available for %s" % 1857 arches=[arch, 'noarch'])
1650 arch, koji.buildLabel(info)) 1858 if pkg.subpackages:
1651 1859 rpms = [d for d in rpms if d['name'] in pkg.subpackages]
1652 rpm_paths = [] 1860 return rpms
1861
1862
1863 def get_pkg_rpm_names(self, pkg, arch=None):
1864 '''
1865 Gets the names for the RPM packages specified in pkg
1866
1867 @type pkg: KojiPkgSpec
1868 @param pkg: a package specification
1869 @type arch: string
1870 @param arch: packages built for this architecture, but also including
1871 architecture independent (noarch) packages
1872 '''
1873 if arch is None:
1874 arch = utils.get_arch()
1875 rpms = self.get_pkg_rpm_info(pkg, arch)
1876 return [rpm['name'] for rpm in rpms]
1877
1878
1879 def get_pkg_rpm_file_names(self, pkg, arch=None):
1880 '''
1881 Gets the file names for the RPM packages specified in pkg
1882
1883 @type pkg: KojiPkgSpec
1884 @param pkg: a package specification
1885 @type arch: string
1886 @param arch: packages built for this architecture, but also including
1887 architecture independent (noarch) packages
1888 '''
1889 if arch is None:
1890 arch = utils.get_arch()
1891 rpm_names = []
1892 rpms = self.get_pkg_rpm_info(pkg, arch)
1893 for rpm in rpms:
1894 arch_rpm_name = koji.pathinfo.rpm(rpm)
1895 rpm_name = os.path.basename(arch_rpm_name)
1896 rpm_names.append(rpm_name)
1897 return rpm_names
1898
1899
1900 def get_pkg_urls(self, pkg, arch=None):
1901 '''
1902 Gets the urls for the packages specified in pkg
1903
1904 @type pkg: KojiPkgSpec
1905 @param pkg: a package specification
1906 @type arch: string
1907 @param arch: packages built for this architecture, but also including
1908 architecture independent (noarch) packages
1909 '''
1910 info = self.get_pkg_info(pkg)
1911 rpms = self.get_pkg_rpm_info(pkg, arch)
1912 rpm_urls = []
1653 for rpm in rpms: 1913 for rpm in rpms:
1654 rpm_name = koji.pathinfo.rpm(rpm) 1914 rpm_name = koji.pathinfo.rpm(rpm)
1655 url = ("%s/%s/%s/%s/%s" % (self.koji_options['pkgurl'], 1915 url = ("%s/%s/%s/%s/%s" % (self.config_options['pkgurl'],
1656 info['package_name'], 1916 info['package_name'],
1657 info['version'], info['release'], 1917 info['version'], info['release'],
1658 rpm_name)) 1918 rpm_name))
1659 if rfilter: 1919 rpm_urls.append(url)
1660 filter_regexp = re.compile(rfilter, re.IGNORECASE) 1920 return rpm_urls
1661 if filter_regexp.match(os.path.basename(rpm_name)): 1921
1662 download = True 1922
1663 else: 1923 def get_pkgs(self, pkg, dst_dir, arch=None):
1664 download = False 1924 '''
1925 Download the packages
1926
1927 @type pkg: KojiPkgSpec
1928 @param pkg: a package specification
1929 @type dst_dir: string
1930 @param dst_dir: the destination directory, where the downloaded
1931 packages will be saved on
1932 @type arch: string
1933 @param arch: packages built for this architecture, but also including
1934 architecture independent (noarch) packages
1935 '''
1936 rpm_urls = self.get_pkg_urls(pkg, arch)
1937 for url in rpm_urls:
1938 utils.get_file(url,
1939 os.path.join(dst_dir, os.path.basename(url)))
1940
1941
1942 DEFAULT_KOJI_TAG = None
1943 def set_default_koji_tag(tag):
1944 '''
1945 Sets the default tag that will be used
1946 '''
1947 global DEFAULT_KOJI_TAG
1948 DEFAULT_KOJI_TAG = tag
1949
1950
1951 def get_default_koji_tag():
1952 return DEFAULT_KOJI_TAG
1953
1954
1955 class KojiPkgSpec:
1956 '''
1957 A package specification syntax parser for Koji
1958
1959 This holds information on either tag or build, and packages to be fetched
1960 from koji and possibly installed (features external do this class).
1961
1962 New objects can be created either by providing information in the textual
1963 format or by using the actual parameters for tag, build, package and sub-
1964 packages. The textual format is useful for command line interfaces and
1965 configuration files, while using parameters is better for using this in
1966 a programatic fashion.
1967
1968 The following sets of examples are interchangeable. Specifying all packages
1969 part of build number 1000:
1970
1971 >>> from kvm_utils import KojiPkgSpec
1972 >>> pkg = KojiPkgSpec('1000')
1973
1974 >>> pkg = KojiPkgSpec(build=1000)
1975
1976 Specifying only a subset of packages of build number 1000:
1977
1978 >>> pkg = KojiPkgSpec('1000:kernel,kernel-devel')
1979
1980 >>> pkg = KojiPkgSpec(build=1000,
1981 subpackages=['kernel', 'kernel-devel'])
1982
1983 Specifying the latest build for the 'kernel' package tagged with 'dist-f14':
1984
1985 >>> pkg = KojiPkgSpec('dist-f14:kernel')
1986
1987 >>> pkg = KojiPkgSpec(tag='dist-f14', package='kernel')
1988
1989 Specifying the 'kernel' package using the default tag:
1990
1991 >>> kvm_utils.set_default_koji_tag('dist-f14')
1992 >>> pkg = KojiPkgSpec('kernel')
1993
1994 >>> pkg = KojiPkgSpec(package='kernel')
1995
1996 Specifying the 'kernel' package using the default tag:
1997
1998 >>> kvm_utils.set_default_koji_tag('dist-f14')
1999 >>> pkg = KojiPkgSpec('kernel')
2000
2001 >>> pkg = KojiPkgSpec(package='kernel')
2002
2003 If you do not specify a default tag, and give a package name without an
2004 explicit tag, your package specification is considered invalid:
2005
2006 >>> print kvm_utils.get_default_koji_tag()
2007 None
2008 >>> print kvm_utils.KojiPkgSpec('kernel').is_valid()
2009 False
2010
2011 >>> print kvm_utils.KojiPkgSpec(package='kernel').is_valid()
2012 False
2013 '''
2014
2015 SEP = ':'
2016
2017 def __init__(self, text='', tag=None, build=None,
2018 package=None, subpackages=[]):
2019 '''
2020 Instantiates a new KojiPkgSpec object
2021
2022 @type text: string
2023 @param text: a textual representation of a package on Koji that
2024 will be parsed
2025 @type tag: string
2026 @param tag: a koji tag, example: Fedora-14-RELEASE
2027 (see U{http://fedoraproject.org/wiki/Koji#Tags_and_Targets})
2028 @type build: number
2029 @param build: a koji build, example: 1001
2030 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture})
2031 @type package: string
2032 @param package: a koji package, example: python
2033 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture})
2034 @type subpackages: list of strings
2035 @param subpackages: a list of package names, usually a subset of
2036 the RPM packages generated by a given build
2037 '''
2038
2039 # Set to None to indicate 'not set' (and be able to use 'is')
2040 self.tag = None
2041 self.build = None
2042 self.package = None
2043 self.subpackages = []
2044
2045 self.default_tag = None
2046
2047 # Textual representation takes precedence (most common use case)
2048 if text:
2049 self.parse(text)
2050 else:
2051 self.tag = tag
2052 self.build = build
2053 self.package = package
2054 self.subpackages = subpackages
2055
2056 # Set the default tag, if set, as a fallback
2057 if not self.build and not self.tag:
2058 default_tag = get_default_koji_tag()
2059 if default_tag is not None:
2060 self.tag = default_tag
2061
2062
2063 def parse(self, text):
2064 '''
2065 Parses a textual representation of a package specification
2066
2067 @type text: string
2068 @param text: textual representation of a package in koji
2069 '''
2070 parts = text.count(self.SEP) + 1
2071 if parts == 1:
2072 if text.isdigit():
2073 self.build = text
1665 else: 2074 else:
1666 download = True 2075 self.package = text
1667 2076 elif parts == 2:
1668 if download: 2077 part1, part2 = text.split(self.SEP)
1669 r = utils.get_file(url, 2078 if part1.isdigit():
1670 os.path.join(dst_dir, os.path.basename(url))) 2079 self.build = part1
1671 rpm_paths.append(r) 2080 self.subpackages = part2.split(',')
1672 2081 else:
1673 return rpm_paths 2082 self.tag = part1
2083 self.package = part2
2084 elif parts >= 3:
2085 # Instead of erroring on more arguments, we simply ignore them
2086 # This makes the parser suitable for future syntax additions, such
2087 # as specifying the package architecture
2088 part1, part2, part3 = text.split(self.SEP)[0:3]
2089 self.tag = part1
2090 self.package = part2
2091 self.subpackages = part3.split(',')
2092
2093
2094 def _is_invalid_neither_tag_or_build(self):
2095 '''
2096 Checks if this package is invalid due to not having either a valid
2097 tag or build set, that is, both are empty.
2098
2099 @returns: True if this is invalid and False if it's valid
2100 '''
2101 return (self.tag is None and self.build is None)
2102
2103
2104 def _is_invalid_package_but_no_tag(self):
2105 '''
2106 Checks if this package is invalid due to having a package name set
2107 but tag or build set, that is, both are empty.
2108
2109 @returns: True if this is invalid and False if it's valid
2110 '''
2111 return (self.package and not self.tag)
2112
2113
2114 def _is_invalid_subpackages_but_no_main_package(self):
2115 '''
2116 Checks if this package is invalid due to having a tag set (this is Ok)
2117 but specifying subpackage names without specifying the main package
2118 name.
2119
2120 Specifying subpackages without a main package name is only valid when
2121 a build is used instead of a tag.
2122
2123 @returns: True if this is invalid and False if it's valid
2124 '''
2125 return (self.tag and self.subpackages and not self.package)
2126
2127
2128 def is_valid(self):
2129 '''
2130 Checks if this package specification is valid.
2131
2132 Being valid means that it has enough and not conflicting information.
2133 It does not validate that the packages specified actually existe on
2134 the Koji server.
2135
2136 @returns: True or False
2137 '''
2138 if self._is_invalid_neither_tag_or_build():
2139 return False
2140 elif self._is_invalid_package_but_no_tag():
2141 return False
2142 elif self._is_invalid_subpackages_but_no_main_package():
2143 return False
2144
2145 return True
2146
2147
2148 def describe_invalid(self):
2149 '''
2150 Describes why this is not valid, in a human friendly way
2151 '''
2152 if self._is_invalid_neither_tag_or_build():
2153 return 'neither a tag or build are set, and of them should be set'
2154 elif self._is_invalid_package_but_no_tag():
2155 return 'package name specified but no tag is set'
2156 elif self._is_invalid_subpackages_but_no_main_package():
2157 return 'subpackages specified but no main package is set'
2158
2159 return 'unkwown reason, seems to be valid'
2160
2161
2162 def describe(self):
2163 '''
2164 Describe this package specification, in a human friendly way
2165
2166 @returns: package specification description
2167 '''
2168 if self.is_valid():
2169 description = ''
2170 if not self.subpackages:
2171 description += 'all subpackages from %s ' % self.package
2172 else:
2173 description += ('only subpackage(s) %s from package %s ' %
2174 (', '.join(self.subpackages), self.package))
2175
2176 if self.build:
2177 description += 'from build %s' % self.build
2178 elif self.tag:
2179 description += 'tagged with %s' % self.tag
2180 else:
2181 raise ValueError, 'neither build or tag is set'
2182
2183 return description
2184 else:
2185 return ('Invalid package specification: %s' %
2186 self.describe_invalid())
2187
2188
2189 def __repr__(self):
2190 return ("<KojiPkgSpec tag=%s build=%s pkg=%s subpkgs=%s>" %
2191 (self.tag, self.build, self.package,
2192 ", ".join(self.subpackages)))
1674 2193
1675 2194
1676 def umount(src, mount_point, type): 2195 def umount(src, mount_point, type):
1677 """ 2196 """
1678 Umount the src mounted in mount_point. 2197 Umount the src mounted in mount_point.
1679 2198
1680 @src: mount source 2199 @src: mount source
1681 @mount_point: mount point 2200 @mount_point: mount point
1682 @type: file system type 2201 @type: file system type
1683 """ 2202 """
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
1719 return False 2238 return False
1720 2239
1721 logging.debug("Verify the mount through /etc/mtab") 2240 logging.debug("Verify the mount through /etc/mtab")
1722 if mount_string in file("/etc/mtab").read(): 2241 if mount_string in file("/etc/mtab").read():
1723 logging.debug("%s is successfully mounted", src) 2242 logging.debug("%s is successfully mounted", src)
1724 return True 2243 return True
1725 else: 2244 else:
1726 logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s", 2245 logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s",
1727 file("/etc/mtab").read()) 2246 file("/etc/mtab").read())
1728 return False 2247 return False
2248
2249
2250 def install_host_kernel(job, params):
2251 """
2252 Install a host kernel, given the appropriate params.
2253
2254 @param job: Job object.
2255 @param params: Dict with host kernel install params.
2256 """
2257 install_type = params.get('host_kernel_install_type')
2258
2259 rpm_url = params.get('host_kernel_rpm_url')
2260
2261 koji_cmd = params.get('host_kernel_koji_cmd')
2262 koji_build = params.get('host_kernel_koji_build')
2263 koji_tag = params.get('host_kernel_koji_tag')
2264
2265 git_repo = params.get('host_kernel_git_repo')
2266 git_branch = params.get('host_kernel_git_branch')
2267 git_commit = params.get('host_kernel_git_commit')
2268 patch_list = params.get('host_kernel_patch_list')
2269 if patch_list:
2270 patch_list = patch_list.split()
2271 kernel_config = params.get('host_kernel_config')
2272
2273 if install_type == 'rpm':
2274 logging.info('Installing host kernel through rpm')
2275 dst = os.path.join("/tmp", os.path.basename(rpm_url))
2276 k = utils.get_file(rpm_url, dst)
2277 host_kernel = job.kernel(k)
2278 host_kernel.install(install_vmlinux=False)
2279 host_kernel.boot()
2280
2281 elif install_type in ['koji', 'brew']:
2282 k_deps = KojiPkgSpec(tag=koji_tag, package='kernel',
2283 subpackages=['kernel-devel', 'kernel-firmware'])
2284 k = KojiPkgSpec(tag=koji_tag, package='kernel',
2285 subpackages=['kernel'])
2286
2287 c = KojiClient(koji_cmd)
2288 logging.info('Fetching kernel dependencies (-devel, -firmware)')
2289 c.get_pkgs(k_deps, job.tmpdir)
2290 logging.info('Installing kernel dependencies (-devel, -firmware) '
2291 'through %s', install_type)
2292 k_deps_rpm_file_names = [os.path.join(job.tmpdir, rpm_file_name) for
2293 rpm_file_name in c.get_pkg_rpm_file_names(k_dep s)]
2294 utils.run('rpm -U --force %s' % " ".join(k_deps_rpm_file_names))
2295
2296 c.get_pkgs(k, job.tmpdir)
2297 k_rpm = os.path.join(job.tmpdir,
2298 c.get_pkg_rpm_file_names(k)[0])
2299 host_kernel = job.kernel(k_rpm)
2300 host_kernel.install(install_vmlinux=False)
2301 host_kernel.boot()
2302
2303 elif install_type == 'git':
2304 logging.info('Chose to install host kernel through git, proceeding')
2305 repodir = os.path.join("/tmp", 'kernel_src')
2306 r = get_git_branch(git_repo, git_branch, repodir, git_commit)
2307 host_kernel = job.kernel(r)
2308 if patch_list:
2309 host_kernel.patch(patch_list)
2310 host_kernel.config(kernel_config)
2311 host_kernel.build()
2312 host_kernel.install()
2313 host_kernel.boot()
2314
2315 else:
2316 logging.info('Chose %s, using the current kernel for the host',
2317 install_type)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698