1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li""" 3*9c5db199SXin LiThis module defines the PackageManager class which provides an 4*9c5db199SXin Liimplementation of the packaging system API providing methods to fetch, 5*9c5db199SXin Liupload and remove packages. 6*9c5db199SXin Li""" 7*9c5db199SXin Li 8*9c5db199SXin Li#pylint: disable=missing-docstring 9*9c5db199SXin Li 10*9c5db199SXin Lifrom __future__ import absolute_import 11*9c5db199SXin Lifrom __future__ import division 12*9c5db199SXin Lifrom __future__ import print_function 13*9c5db199SXin Li 14*9c5db199SXin Liimport fcntl 15*9c5db199SXin Liimport logging 16*9c5db199SXin Liimport os 17*9c5db199SXin Liimport re 18*9c5db199SXin Liimport shutil 19*9c5db199SXin Liimport six 20*9c5db199SXin Li 21*9c5db199SXin Liimport common 22*9c5db199SXin Lifrom autotest_lib.client.bin import os_dep 23*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 24*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config 25*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils 26*9c5db199SXin Li 27*9c5db199SXin Li 28*9c5db199SXin Li# the name of the checksum file that stores the packages' checksums 29*9c5db199SXin LiCHECKSUM_FILE = "packages.checksum" 30*9c5db199SXin Li 31*9c5db199SXin Li 32*9c5db199SXin Lidef has_pbzip2(): 33*9c5db199SXin Li '''Check if parallel bzip2 is available on this system.''' 34*9c5db199SXin Li try: 35*9c5db199SXin Li os_dep.command('pbzip2') 36*9c5db199SXin Li except ValueError: 37*9c5db199SXin Li return False 38*9c5db199SXin Li return True 39*9c5db199SXin Li 40*9c5db199SXin Li 41*9c5db199SXin Li# is parallel bzip2 available for use? 42*9c5db199SXin Li_PBZIP2_AVAILABLE = has_pbzip2() 43*9c5db199SXin Li 44*9c5db199SXin Li 45*9c5db199SXin Lidef parse_ssh_path(repo): 46*9c5db199SXin Li ''' 47*9c5db199SXin Li Parse ssh://xx@xx/path/to/ and return a tuple with host_line and 48*9c5db199SXin Li remote path 49*9c5db199SXin Li ''' 50*9c5db199SXin Li 51*9c5db199SXin Li match = re.search('^ssh://(.*?)(/.*)$', repo) 52*9c5db199SXin Li if match: 53*9c5db199SXin Li return match.groups() 54*9c5db199SXin Li else: 55*9c5db199SXin Li raise error.PackageUploadError( 56*9c5db199SXin Li "Incorrect SSH path in global_config: %s" % repo) 57*9c5db199SXin Li 58*9c5db199SXin Li 59*9c5db199SXin Lidef repo_run_command(repo, cmd, ignore_status=False, cd=True): 60*9c5db199SXin Li """Run a command relative to the repos path""" 61*9c5db199SXin Li repo = repo.strip() 62*9c5db199SXin Li run_cmd = None 63*9c5db199SXin Li cd_str = '' 64*9c5db199SXin Li if repo.startswith('ssh://'): 65*9c5db199SXin Li username = None 66*9c5db199SXin Li hostline, remote_path = parse_ssh_path(repo) 67*9c5db199SXin Li if cd: 68*9c5db199SXin Li cd_str = 'cd %s && ' % remote_path 69*9c5db199SXin Li if '@' in hostline: 70*9c5db199SXin Li username, host = hostline.split('@') 71*9c5db199SXin Li run_cmd = 'ssh %s@%s "%s%s"' % (username, host, cd_str, cmd) 72*9c5db199SXin Li else: 73*9c5db199SXin Li run_cmd = 'ssh %s "%s%s"' % (host, cd_str, cmd) 74*9c5db199SXin Li 75*9c5db199SXin Li else: 76*9c5db199SXin Li if cd: 77*9c5db199SXin Li cd_str = 'cd %s && ' % repo 78*9c5db199SXin Li run_cmd = "%s%s" % (cd_str, cmd) 79*9c5db199SXin Li 80*9c5db199SXin Li if run_cmd: 81*9c5db199SXin Li return utils.run(run_cmd, ignore_status=ignore_status) 82*9c5db199SXin Li 83*9c5db199SXin Li 84*9c5db199SXin Lidef create_directory(repo): 85*9c5db199SXin Li remote_path = repo 86*9c5db199SXin Li if repo.startswith('ssh://'): 87*9c5db199SXin Li _, remote_path = parse_ssh_path(repo) 88*9c5db199SXin Li repo_run_command(repo, 'mkdir -p %s' % remote_path, cd=False) 89*9c5db199SXin Li 90*9c5db199SXin Li 91*9c5db199SXin Lidef check_diskspace(repo, min_free=None): 92*9c5db199SXin Li # Note: 1 GB = 10**9 bytes (SI unit). 93*9c5db199SXin Li if min_free is None: 94*9c5db199SXin Li min_free = global_config.global_config.get_config_value('PACKAGES', 95*9c5db199SXin Li 'minimum_free_space', 96*9c5db199SXin Li type=int, default=1) 97*9c5db199SXin Li try: 98*9c5db199SXin Li df = repo_run_command(repo, 99*9c5db199SXin Li 'df -PB %d . | tail -1' % 10 ** 9).stdout.split() 100*9c5db199SXin Li free_space_gb = int(df[3]) 101*9c5db199SXin Li except Exception as e: 102*9c5db199SXin Li raise error.RepoUnknownError('Unknown Repo Error: %s' % e) 103*9c5db199SXin Li if free_space_gb < min_free: 104*9c5db199SXin Li raise error.RepoDiskFullError('Not enough disk space available ' 105*9c5db199SXin Li '%sg < %sg' % (free_space_gb, min_free)) 106*9c5db199SXin Li 107*9c5db199SXin Li 108*9c5db199SXin Lidef check_write(repo): 109*9c5db199SXin Li try: 110*9c5db199SXin Li repo_testfile = '.repo_test_file' 111*9c5db199SXin Li repo_run_command(repo, 'touch %s' % repo_testfile).stdout.strip() 112*9c5db199SXin Li repo_run_command(repo, 'rm ' + repo_testfile) 113*9c5db199SXin Li except error.CmdError: 114*9c5db199SXin Li raise error.RepoWriteError('Unable to write to ' + repo) 115*9c5db199SXin Li 116*9c5db199SXin Li 117*9c5db199SXin Lidef trim_custom_directories(repo, older_than_days=None): 118*9c5db199SXin Li if not repo: 119*9c5db199SXin Li return 120*9c5db199SXin Li 121*9c5db199SXin Li if older_than_days is None: 122*9c5db199SXin Li older_than_days = global_config.global_config.get_config_value( 123*9c5db199SXin Li 'PACKAGES', 'custom_max_age', type=int, default=40) 124*9c5db199SXin Li cmd = 'find . -type f -atime +%s -exec rm -f {} \;' % older_than_days 125*9c5db199SXin Li repo_run_command(repo, cmd, ignore_status=True) 126*9c5db199SXin Li 127*9c5db199SXin Li 128*9c5db199SXin Liclass RepositoryFetcher(object): 129*9c5db199SXin Li url = None 130*9c5db199SXin Li 131*9c5db199SXin Li 132*9c5db199SXin Li def fetch_pkg_file(self, filename, dest_path): 133*9c5db199SXin Li """ Fetch a package file from a package repository. 134*9c5db199SXin Li 135*9c5db199SXin Li @param filename: The filename of the package file to fetch. 136*9c5db199SXin Li @param dest_path: Destination path to download the file to. 137*9c5db199SXin Li 138*9c5db199SXin Li @raises PackageFetchError if the fetch failed 139*9c5db199SXin Li """ 140*9c5db199SXin Li raise NotImplementedError() 141*9c5db199SXin Li 142*9c5db199SXin Li 143*9c5db199SXin Liclass HttpFetcher(RepositoryFetcher): 144*9c5db199SXin Li curl_cmd_pattern = 'curl --connect-timeout 15 -s %s -o %s' 145*9c5db199SXin Li 146*9c5db199SXin Li 147*9c5db199SXin Li def __init__(self, package_manager, repository_url): 148*9c5db199SXin Li """ 149*9c5db199SXin Li @param repository_url: The base URL of the http repository 150*9c5db199SXin Li """ 151*9c5db199SXin Li self.run_command = package_manager._run_command 152*9c5db199SXin Li self.url = repository_url 153*9c5db199SXin Li 154*9c5db199SXin Li def exists(self, destpath, target='file'): 155*9c5db199SXin Li """Check if a file or directory exists using `test`. 156*9c5db199SXin Li 157*9c5db199SXin Li This is a wrapper for run_command. 158*9c5db199SXin Li 159*9c5db199SXin Li Args: 160*9c5db199SXin Li target: Optional string that should either be 'file' or 'dir' 161*9c5db199SXin Li indicating what should exist. 162*9c5db199SXin Li """ 163*9c5db199SXin Li if target == 'dir': 164*9c5db199SXin Li test_cmd = 'test -d %s' 165*9c5db199SXin Li else: 166*9c5db199SXin Li test_cmd = 'test -e %s' 167*9c5db199SXin Li 168*9c5db199SXin Li try: 169*9c5db199SXin Li self.run_command(test_cmd % destpath) 170*9c5db199SXin Li return True 171*9c5db199SXin Li except (error.CmdError, error.AutoservRunError): 172*9c5db199SXin Li return False 173*9c5db199SXin Li 174*9c5db199SXin Li def _quick_http_test(self): 175*9c5db199SXin Li """ Run a simple 30 second curl on the repository to see if it is 176*9c5db199SXin Li reachable. This avoids the need to wait for a full 10min timeout. 177*9c5db199SXin Li """ 178*9c5db199SXin Li # just make a temp file to write a test fetch into 179*9c5db199SXin Li mktemp = 'mktemp -u /tmp/tmp.XXXXXX' 180*9c5db199SXin Li dest_file_path = self.run_command(mktemp).stdout.strip() 181*9c5db199SXin Li 182*9c5db199SXin Li try: 183*9c5db199SXin Li # build up a curl command 184*9c5db199SXin Li http_cmd = self.curl_cmd_pattern % (self.url, dest_file_path) 185*9c5db199SXin Li try: 186*9c5db199SXin Li self.run_command(http_cmd, _run_command_dargs={'timeout': 30}) 187*9c5db199SXin Li except Exception as e: 188*9c5db199SXin Li msg = 'HTTP test failed, unable to contact %s: %s' 189*9c5db199SXin Li raise error.PackageFetchError(msg % (self.url, e)) 190*9c5db199SXin Li finally: 191*9c5db199SXin Li self.run_command('rm -rf %s' % dest_file_path) 192*9c5db199SXin Li 193*9c5db199SXin Li 194*9c5db199SXin Li def fetch_pkg_file(self, filename, dest_path): 195*9c5db199SXin Li logging.info('Fetching %s from %s to %s', filename, self.url, 196*9c5db199SXin Li dest_path) 197*9c5db199SXin Li 198*9c5db199SXin Li # do a quick test to verify the repo is reachable 199*9c5db199SXin Li self._quick_http_test() 200*9c5db199SXin Li 201*9c5db199SXin Li # try to retrieve the package via http 202*9c5db199SXin Li package_url = os.path.join(self.url, filename) 203*9c5db199SXin Li try: 204*9c5db199SXin Li cmd = self.curl_cmd_pattern % (package_url, dest_path) 205*9c5db199SXin Li result = self.run_command(cmd, 206*9c5db199SXin Li _run_command_dargs={'timeout': 1200}) 207*9c5db199SXin Li 208*9c5db199SXin Li if not self.exists(dest_path): 209*9c5db199SXin Li logging.error('curl failed: %s', result) 210*9c5db199SXin Li raise error.CmdError(cmd, result) 211*9c5db199SXin Li 212*9c5db199SXin Li logging.info('Successfully fetched %s from %s', filename, 213*9c5db199SXin Li package_url) 214*9c5db199SXin Li except error.CmdError as e: 215*9c5db199SXin Li # remove whatever junk was retrieved when the get failed 216*9c5db199SXin Li self.run_command('rm -f %s' % dest_path) 217*9c5db199SXin Li 218*9c5db199SXin Li raise error.PackageFetchError('%s not found in %s\n%s' 219*9c5db199SXin Li 'curl error code: %d' % (filename, package_url, 220*9c5db199SXin Li e.result_obj.stderr, e.result_obj.exit_status)) 221*9c5db199SXin Li 222*9c5db199SXin Li 223*9c5db199SXin Liclass LocalFilesystemFetcher(RepositoryFetcher): 224*9c5db199SXin Li def __init__(self, package_manager, local_dir): 225*9c5db199SXin Li self.run_command = package_manager._run_command 226*9c5db199SXin Li self.url = local_dir 227*9c5db199SXin Li 228*9c5db199SXin Li 229*9c5db199SXin Li def fetch_pkg_file(self, filename, dest_path): 230*9c5db199SXin Li logging.info('Fetching %s from %s to %s', filename, self.url, 231*9c5db199SXin Li dest_path) 232*9c5db199SXin Li local_path = os.path.join(self.url, filename) 233*9c5db199SXin Li try: 234*9c5db199SXin Li self.run_command('cp %s %s' % (local_path, dest_path)) 235*9c5db199SXin Li logging.debug('Successfully fetched %s from %s', filename, 236*9c5db199SXin Li local_path) 237*9c5db199SXin Li except error.CmdError as e: 238*9c5db199SXin Li raise error.PackageFetchError( 239*9c5db199SXin Li 'Package %s could not be fetched from %s' 240*9c5db199SXin Li % (filename, self.url), e) 241*9c5db199SXin Li 242*9c5db199SXin Li 243*9c5db199SXin Liclass BasePackageManager(object): 244*9c5db199SXin Li def __init__(self, pkgmgr_dir, hostname=None, repo_urls=None, 245*9c5db199SXin Li upload_paths=None, do_locking=True, run_function=utils.run, 246*9c5db199SXin Li run_function_args=[], run_function_dargs={}): 247*9c5db199SXin Li ''' 248*9c5db199SXin Li repo_urls: The list of the repository urls which is consulted 249*9c5db199SXin Li whilst fetching the package 250*9c5db199SXin Li upload_paths: The list of the upload of repositories to which 251*9c5db199SXin Li the package is uploaded to 252*9c5db199SXin Li pkgmgr_dir : A directory that can be used by the package manager 253*9c5db199SXin Li to dump stuff (like checksum files of the repositories 254*9c5db199SXin Li etc.). 255*9c5db199SXin Li do_locking : Enable locking when the packages are installed. 256*9c5db199SXin Li 257*9c5db199SXin Li run_function is used to execute the commands throughout this file. 258*9c5db199SXin Li It defaults to utils.run() but a custom method (if provided) should 259*9c5db199SXin Li be of the same schema as utils.run. It should return a CmdResult 260*9c5db199SXin Li object and throw a CmdError exception. The reason for using a separate 261*9c5db199SXin Li function to run the commands is that the same code can be run to fetch 262*9c5db199SXin Li a package on the local machine or on a remote machine (in which case 263*9c5db199SXin Li ssh_host's run function is passed in for run_function). 264*9c5db199SXin Li ''' 265*9c5db199SXin Li # In memory dictionary that stores the checksum's of packages 266*9c5db199SXin Li self._checksum_dict = {} 267*9c5db199SXin Li 268*9c5db199SXin Li self.pkgmgr_dir = pkgmgr_dir 269*9c5db199SXin Li self.do_locking = do_locking 270*9c5db199SXin Li self.hostname = hostname 271*9c5db199SXin Li self.repositories = [] 272*9c5db199SXin Li 273*9c5db199SXin Li # Create an internal function that is a simple wrapper of 274*9c5db199SXin Li # run_function and takes in the args and dargs as arguments 275*9c5db199SXin Li def _run_command(command, _run_command_args=run_function_args, 276*9c5db199SXin Li _run_command_dargs={}): 277*9c5db199SXin Li ''' 278*9c5db199SXin Li Special internal function that takes in a command as 279*9c5db199SXin Li argument and passes it on to run_function (if specified). 280*9c5db199SXin Li The _run_command_dargs are merged into run_function_dargs 281*9c5db199SXin Li with the former having more precedence than the latter. 282*9c5db199SXin Li ''' 283*9c5db199SXin Li new_dargs = dict(run_function_dargs) 284*9c5db199SXin Li new_dargs.update(_run_command_dargs) 285*9c5db199SXin Li # avoid polluting logs with extremely verbose packaging output 286*9c5db199SXin Li new_dargs.update({'stdout_tee' : None}) 287*9c5db199SXin Li 288*9c5db199SXin Li return run_function(command, *_run_command_args, 289*9c5db199SXin Li **new_dargs) 290*9c5db199SXin Li 291*9c5db199SXin Li self._run_command = _run_command 292*9c5db199SXin Li 293*9c5db199SXin Li # Process the repository URLs 294*9c5db199SXin Li if not repo_urls: 295*9c5db199SXin Li repo_urls = [] 296*9c5db199SXin Li elif hostname: 297*9c5db199SXin Li repo_urls = self.get_mirror_list(repo_urls) 298*9c5db199SXin Li for url in repo_urls: 299*9c5db199SXin Li self.add_repository(url) 300*9c5db199SXin Li 301*9c5db199SXin Li # Process the upload URLs 302*9c5db199SXin Li if not upload_paths: 303*9c5db199SXin Li self.upload_paths = [] 304*9c5db199SXin Li else: 305*9c5db199SXin Li self.upload_paths = list(upload_paths) 306*9c5db199SXin Li 307*9c5db199SXin Li 308*9c5db199SXin Li def add_repository(self, repo): 309*9c5db199SXin Li if isinstance(repo, six.string_types): 310*9c5db199SXin Li self.repositories.append(self.get_fetcher(repo)) 311*9c5db199SXin Li elif isinstance(repo, RepositoryFetcher): 312*9c5db199SXin Li self.repositories.append(repo) 313*9c5db199SXin Li else: 314*9c5db199SXin Li raise TypeError("repo must be RepositoryFetcher or url string") 315*9c5db199SXin Li 316*9c5db199SXin Li def exists(self, destpath, target='file'): 317*9c5db199SXin Li """Check if a file or directory exists using `test`. 318*9c5db199SXin Li 319*9c5db199SXin Li This is a wrapper for _run_command. 320*9c5db199SXin Li 321*9c5db199SXin Li Args: 322*9c5db199SXin Li target: Optional string that should either be 'file' or 'dir' 323*9c5db199SXin Li indicating what should exist. 324*9c5db199SXin Li """ 325*9c5db199SXin Li if target == 'dir': 326*9c5db199SXin Li test_cmd = 'test -d %s' 327*9c5db199SXin Li else: 328*9c5db199SXin Li test_cmd = 'test -e %s' 329*9c5db199SXin Li 330*9c5db199SXin Li try: 331*9c5db199SXin Li self._run_command(test_cmd % destpath) 332*9c5db199SXin Li return True 333*9c5db199SXin Li except (error.CmdError, error.AutoservRunError): 334*9c5db199SXin Li return False 335*9c5db199SXin Li 336*9c5db199SXin Li def get_fetcher(self, url): 337*9c5db199SXin Li if url.startswith('http://'): 338*9c5db199SXin Li return HttpFetcher(self, url) 339*9c5db199SXin Li else: 340*9c5db199SXin Li return LocalFilesystemFetcher(self, url) 341*9c5db199SXin Li 342*9c5db199SXin Li 343*9c5db199SXin Li def repo_check(self, repo): 344*9c5db199SXin Li ''' 345*9c5db199SXin Li Check to make sure the repo is in a valid state: 346*9c5db199SXin Li ensure we have at least XX amount of free space 347*9c5db199SXin Li Make sure we can write to the repo 348*9c5db199SXin Li ''' 349*9c5db199SXin Li if not repo.startswith('/') and not repo.startswith('ssh:'): 350*9c5db199SXin Li return 351*9c5db199SXin Li try: 352*9c5db199SXin Li create_directory(repo) 353*9c5db199SXin Li check_diskspace(repo) 354*9c5db199SXin Li check_write(repo) 355*9c5db199SXin Li except (error.RepoWriteError, error.RepoUnknownError, 356*9c5db199SXin Li error.RepoDiskFullError) as e: 357*9c5db199SXin Li raise error.RepoError("ERROR: Repo %s: %s" % (repo, e)) 358*9c5db199SXin Li 359*9c5db199SXin Li 360*9c5db199SXin Li def upkeep(self, custom_repos=None): 361*9c5db199SXin Li ''' 362*9c5db199SXin Li Clean up custom upload/download areas 363*9c5db199SXin Li ''' 364*9c5db199SXin Li from autotest_lib.server import subcommand 365*9c5db199SXin Li if not custom_repos: 366*9c5db199SXin Li # Not all package types necessarily require or allow custom repos 367*9c5db199SXin Li try: 368*9c5db199SXin Li custom_repos = global_config.global_config.get_config_value( 369*9c5db199SXin Li 'PACKAGES', 'custom_upload_location').split(',') 370*9c5db199SXin Li except global_config.ConfigError: 371*9c5db199SXin Li custom_repos = [] 372*9c5db199SXin Li try: 373*9c5db199SXin Li custom_download = global_config.global_config.get_config_value( 374*9c5db199SXin Li 'PACKAGES', 'custom_download_location') 375*9c5db199SXin Li custom_repos += [custom_download] 376*9c5db199SXin Li except global_config.ConfigError: 377*9c5db199SXin Li pass 378*9c5db199SXin Li 379*9c5db199SXin Li if not custom_repos: 380*9c5db199SXin Li return 381*9c5db199SXin Li 382*9c5db199SXin Li subcommand.parallel_simple(trim_custom_directories, custom_repos, 383*9c5db199SXin Li log=False) 384*9c5db199SXin Li 385*9c5db199SXin Li 386*9c5db199SXin Li def install_pkg(self, name, pkg_type, fetch_dir, install_dir, 387*9c5db199SXin Li preserve_install_dir=False, repo_url=None): 388*9c5db199SXin Li ''' 389*9c5db199SXin Li Remove install_dir if it already exists and then recreate it unless 390*9c5db199SXin Li preserve_install_dir is specified as True. 391*9c5db199SXin Li Fetch the package into the pkg_dir. Untar the package into install_dir 392*9c5db199SXin Li The assumption is that packages are of the form : 393*9c5db199SXin Li <pkg_type>.<pkg_name>.tar.bz2 394*9c5db199SXin Li name : name of the package 395*9c5db199SXin Li type : type of the package 396*9c5db199SXin Li fetch_dir : The directory into which the package tarball will be 397*9c5db199SXin Li fetched to. 398*9c5db199SXin Li install_dir : the directory where the package files will be untarred to 399*9c5db199SXin Li repo_url : the url of the repository to fetch the package from. 400*9c5db199SXin Li ''' 401*9c5db199SXin Li 402*9c5db199SXin Li # do_locking flag is on by default unless you disable it (typically 403*9c5db199SXin Li # in the cases where packages are directly installed from the server 404*9c5db199SXin Li # onto the client in which case fcntl stuff wont work as the code 405*9c5db199SXin Li # will run on the server in that case.. 406*9c5db199SXin Li if self.do_locking: 407*9c5db199SXin Li lockfile_name = '.%s-%s-lock' % (name, pkg_type) 408*9c5db199SXin Li lockfile = open(os.path.join(self.pkgmgr_dir, lockfile_name), 'w') 409*9c5db199SXin Li 410*9c5db199SXin Li try: 411*9c5db199SXin Li if self.do_locking: 412*9c5db199SXin Li fcntl.flock(lockfile, fcntl.LOCK_EX) 413*9c5db199SXin Li 414*9c5db199SXin Li self._run_command('mkdir -p %s' % fetch_dir) 415*9c5db199SXin Li 416*9c5db199SXin Li pkg_name = self.get_tarball_name(name, pkg_type) 417*9c5db199SXin Li fetch_path = os.path.join(fetch_dir, pkg_name) 418*9c5db199SXin Li try: 419*9c5db199SXin Li # Fetch the package into fetch_dir 420*9c5db199SXin Li self.fetch_pkg(pkg_name, fetch_path, use_checksum=True) 421*9c5db199SXin Li 422*9c5db199SXin Li # check to see if the install_dir exists and if it does 423*9c5db199SXin Li # then check to see if the .checksum file is the latest 424*9c5db199SXin Li if (self.exists(install_dir, target='dir') and 425*9c5db199SXin Li not self.untar_required(fetch_path, install_dir)): 426*9c5db199SXin Li return 427*9c5db199SXin Li 428*9c5db199SXin Li # untar the package into install_dir and 429*9c5db199SXin Li # update the checksum in that directory 430*9c5db199SXin Li if not preserve_install_dir: 431*9c5db199SXin Li # Make sure we clean up the install_dir 432*9c5db199SXin Li self._run_command('rm -rf %s' % install_dir) 433*9c5db199SXin Li self._run_command('mkdir -p %s' % install_dir) 434*9c5db199SXin Li 435*9c5db199SXin Li self.untar_pkg(fetch_path, install_dir) 436*9c5db199SXin Li 437*9c5db199SXin Li except error.PackageFetchError as why: 438*9c5db199SXin Li raise error.PackageInstallError( 439*9c5db199SXin Li 'Installation of %s(type:%s) failed : %s' 440*9c5db199SXin Li % (name, pkg_type, why)) 441*9c5db199SXin Li finally: 442*9c5db199SXin Li if self.do_locking: 443*9c5db199SXin Li fcntl.flock(lockfile, fcntl.LOCK_UN) 444*9c5db199SXin Li lockfile.close() 445*9c5db199SXin Li 446*9c5db199SXin Li 447*9c5db199SXin Li def fetch_pkg(self, pkg_name, dest_path, repo_url=None, use_checksum=False): 448*9c5db199SXin Li ''' 449*9c5db199SXin Li Fetch the package into dest_dir from repo_url. By default repo_url 450*9c5db199SXin Li is None and the package is looked in all the repositories specified. 451*9c5db199SXin Li Otherwise it fetches it from the specific repo_url. 452*9c5db199SXin Li pkg_name : name of the package (ex: test-sleeptest.tar.bz2, 453*9c5db199SXin Li dep-gcc.tar.bz2, kernel.1-1.rpm) 454*9c5db199SXin Li repo_url : the URL of the repository where the package is located. 455*9c5db199SXin Li dest_path : complete path of where the package will be fetched to. 456*9c5db199SXin Li use_checksum : This is set to False to fetch the packages.checksum file 457*9c5db199SXin Li so that the checksum comparison is bypassed for the 458*9c5db199SXin Li checksum file itself. This is used internally by the 459*9c5db199SXin Li packaging system. It should be ignored by externals 460*9c5db199SXin Li callers of this method who use it fetch custom packages. 461*9c5db199SXin Li ''' 462*9c5db199SXin Li # Check if the destination dir exists. 463*9c5db199SXin Li if not self.exists(os.path.dirname(dest_path), target='dir'): 464*9c5db199SXin Li raise error.PackageFetchError("Please provide a valid " 465*9c5db199SXin Li "destination: %s " % dest_path) 466*9c5db199SXin Li 467*9c5db199SXin Li # See if the package was already fetched earlier, if so 468*9c5db199SXin Li # the checksums need to be compared and the package is now 469*9c5db199SXin Li # fetched only if they differ. 470*9c5db199SXin Li pkg_exists = self.exists(dest_path) 471*9c5db199SXin Li 472*9c5db199SXin Li # if a repository location is explicitly provided, fetch the package 473*9c5db199SXin Li # from there and return 474*9c5db199SXin Li if repo_url: 475*9c5db199SXin Li repositories = [self.get_fetcher(repo_url)] 476*9c5db199SXin Li elif self.repositories: 477*9c5db199SXin Li repositories = self.repositories 478*9c5db199SXin Li else: 479*9c5db199SXin Li raise error.PackageFetchError("No repository urls specified") 480*9c5db199SXin Li 481*9c5db199SXin Li # install the package from the package repos, try the repos in 482*9c5db199SXin Li # reverse order, assuming that the 'newest' repos are most desirable 483*9c5db199SXin Li for fetcher in reversed(repositories): 484*9c5db199SXin Li try: 485*9c5db199SXin Li # Fetch the package if it is not there, the checksum does 486*9c5db199SXin Li # not match, or checksums are disabled entirely 487*9c5db199SXin Li need_to_fetch = ( 488*9c5db199SXin Li not use_checksum or not pkg_exists 489*9c5db199SXin Li or not self.compare_checksum(dest_path)) 490*9c5db199SXin Li if need_to_fetch: 491*9c5db199SXin Li fetcher.fetch_pkg_file(pkg_name, dest_path) 492*9c5db199SXin Li # update checksum so we won't refetch next time. 493*9c5db199SXin Li if use_checksum: 494*9c5db199SXin Li self.update_checksum(dest_path) 495*9c5db199SXin Li return 496*9c5db199SXin Li except (error.PackageFetchError, error.AutoservRunError) as e: 497*9c5db199SXin Li # The package could not be found in this repo, continue looking 498*9c5db199SXin Li logging.debug(e) 499*9c5db199SXin Li 500*9c5db199SXin Li repo_url_list = [repo.url for repo in repositories] 501*9c5db199SXin Li message = ('%s could not be fetched from any of the repos %s' % 502*9c5db199SXin Li (pkg_name, repo_url_list)) 503*9c5db199SXin Li logging.debug(message) 504*9c5db199SXin Li # if we got here then that means the package is not found 505*9c5db199SXin Li # in any of the repositories. 506*9c5db199SXin Li raise error.PackageFetchError(message) 507*9c5db199SXin Li 508*9c5db199SXin Li 509*9c5db199SXin Li def upload_pkg(self, pkg_path, upload_path=None, update_checksum=False, 510*9c5db199SXin Li timeout=300): 511*9c5db199SXin Li from autotest_lib.server import subcommand 512*9c5db199SXin Li if upload_path: 513*9c5db199SXin Li upload_path_list = [upload_path] 514*9c5db199SXin Li self.upkeep(upload_path_list) 515*9c5db199SXin Li elif len(self.upload_paths) > 0: 516*9c5db199SXin Li self.upkeep() 517*9c5db199SXin Li upload_path_list = self.upload_paths 518*9c5db199SXin Li else: 519*9c5db199SXin Li raise error.PackageUploadError("Invalid Upload Path specified") 520*9c5db199SXin Li 521*9c5db199SXin Li if update_checksum: 522*9c5db199SXin Li # get the packages' checksum file and update it with the current 523*9c5db199SXin Li # package's checksum 524*9c5db199SXin Li self.update_checksum(pkg_path) 525*9c5db199SXin Li 526*9c5db199SXin Li commands = [] 527*9c5db199SXin Li for path in upload_path_list: 528*9c5db199SXin Li commands.append(subcommand.subcommand(self.upload_pkg_parallel, 529*9c5db199SXin Li (pkg_path, path, 530*9c5db199SXin Li update_checksum))) 531*9c5db199SXin Li 532*9c5db199SXin Li results = subcommand.parallel(commands, timeout, return_results=True) 533*9c5db199SXin Li for result in results: 534*9c5db199SXin Li if result: 535*9c5db199SXin Li print(str(result)) 536*9c5db199SXin Li 537*9c5db199SXin Li 538*9c5db199SXin Li # TODO(aganti): Fix the bug with the current checksum logic where 539*9c5db199SXin Li # packages' checksums that are not present consistently in all the 540*9c5db199SXin Li # repositories are not handled properly. This is a corner case though 541*9c5db199SXin Li # but the ideal solution is to make the checksum file repository specific 542*9c5db199SXin Li # and then maintain it. 543*9c5db199SXin Li def upload_pkg_parallel(self, pkg_path, upload_path, update_checksum=False): 544*9c5db199SXin Li ''' 545*9c5db199SXin Li Uploads to a specified upload_path or to all the repos. 546*9c5db199SXin Li Also uploads the checksum file to all the repos. 547*9c5db199SXin Li pkg_path : The complete path to the package file 548*9c5db199SXin Li upload_path : the absolute path where the files are copied to. 549*9c5db199SXin Li if set to 'None' assumes 'all' repos 550*9c5db199SXin Li update_checksum : If set to False, the checksum file is not 551*9c5db199SXin Li going to be updated which happens by default. 552*9c5db199SXin Li This is necessary for custom 553*9c5db199SXin Li packages (like custom kernels and custom tests) 554*9c5db199SXin Li that get uploaded which do not need to be part of 555*9c5db199SXin Li the checksum file and bloat it. 556*9c5db199SXin Li ''' 557*9c5db199SXin Li self.repo_check(upload_path) 558*9c5db199SXin Li # upload the package 559*9c5db199SXin Li if os.path.isdir(pkg_path): 560*9c5db199SXin Li self.upload_pkg_dir(pkg_path, upload_path) 561*9c5db199SXin Li else: 562*9c5db199SXin Li self.upload_pkg_file(pkg_path, upload_path) 563*9c5db199SXin Li if update_checksum: 564*9c5db199SXin Li self.upload_pkg_file(self._get_checksum_file_path(), 565*9c5db199SXin Li upload_path) 566*9c5db199SXin Li 567*9c5db199SXin Li 568*9c5db199SXin Li def upload_pkg_file(self, file_path, upload_path): 569*9c5db199SXin Li ''' 570*9c5db199SXin Li Upload a single file. Depending on the upload path, the appropriate 571*9c5db199SXin Li method for that protocol is called. Currently this simply copies the 572*9c5db199SXin Li file to the target directory (but can be extended for other protocols) 573*9c5db199SXin Li This assumes that the web server is running on the same machine where 574*9c5db199SXin Li the method is being called from. The upload_path's files are 575*9c5db199SXin Li basically served by that web server. 576*9c5db199SXin Li ''' 577*9c5db199SXin Li try: 578*9c5db199SXin Li if upload_path.startswith('ssh://'): 579*9c5db199SXin Li # parse ssh://user@host/usr/local/autotest/packages 580*9c5db199SXin Li hostline, remote_path = parse_ssh_path(upload_path) 581*9c5db199SXin Li try: 582*9c5db199SXin Li utils.run('scp %s %s:%s' % (file_path, hostline, 583*9c5db199SXin Li remote_path)) 584*9c5db199SXin Li r_path = os.path.join(remote_path, 585*9c5db199SXin Li os.path.basename(file_path)) 586*9c5db199SXin Li utils.run("ssh %s 'chmod 644 %s'" % (hostline, r_path)) 587*9c5db199SXin Li except error.CmdError: 588*9c5db199SXin Li logging.error("Error uploading to repository %s", 589*9c5db199SXin Li upload_path) 590*9c5db199SXin Li else: 591*9c5db199SXin Li # Delete any older version of the package that might exist. 592*9c5db199SXin Li orig_file = os.path.join(upload_path, 593*9c5db199SXin Li os.path.basename(file_path)) 594*9c5db199SXin Li if os.path.exists(orig_file): 595*9c5db199SXin Li os.remove(orig_file) 596*9c5db199SXin Li 597*9c5db199SXin Li shutil.copy(file_path, upload_path) 598*9c5db199SXin Li os.chmod(orig_file, 0o644) 599*9c5db199SXin Li except (IOError, os.error) as why: 600*9c5db199SXin Li logging.error("Upload of %s to %s failed: %s", file_path, 601*9c5db199SXin Li upload_path, why) 602*9c5db199SXin Li 603*9c5db199SXin Li 604*9c5db199SXin Li def upload_pkg_dir(self, dir_path, upload_path): 605*9c5db199SXin Li ''' 606*9c5db199SXin Li Upload a full directory. Depending on the upload path, the appropriate 607*9c5db199SXin Li method for that protocol is called. Currently this copies the whole 608*9c5db199SXin Li tmp package directory to the target directory. 609*9c5db199SXin Li This assumes that the web server is running on the same machine where 610*9c5db199SXin Li the method is being called from. The upload_path's files are 611*9c5db199SXin Li basically served by that web server. 612*9c5db199SXin Li ''' 613*9c5db199SXin Li local_path = os.path.join(dir_path, "*") 614*9c5db199SXin Li try: 615*9c5db199SXin Li if upload_path.startswith('ssh://'): 616*9c5db199SXin Li hostline, remote_path = parse_ssh_path(upload_path) 617*9c5db199SXin Li try: 618*9c5db199SXin Li utils.run('scp %s %s:%s' % (local_path, hostline, 619*9c5db199SXin Li remote_path)) 620*9c5db199SXin Li ssh_path = os.path.join(remote_path, "*") 621*9c5db199SXin Li utils.run("ssh %s 'chmod 644 %s'" % (hostline, ssh_path)) 622*9c5db199SXin Li except error.CmdError: 623*9c5db199SXin Li logging.error("Error uploading to repository: %s", 624*9c5db199SXin Li upload_path) 625*9c5db199SXin Li else: 626*9c5db199SXin Li utils.run("cp %s %s " % (local_path, upload_path)) 627*9c5db199SXin Li up_path = os.path.join(upload_path, "*") 628*9c5db199SXin Li utils.run("chmod 644 %s" % up_path) 629*9c5db199SXin Li except (IOError, os.error) as why: 630*9c5db199SXin Li raise error.PackageUploadError("Upload of %s to %s failed: %s" 631*9c5db199SXin Li % (dir_path, upload_path, why)) 632*9c5db199SXin Li 633*9c5db199SXin Li 634*9c5db199SXin Li def remove_pkg(self, pkg_name, remove_path=None, remove_checksum=False): 635*9c5db199SXin Li ''' 636*9c5db199SXin Li Remove the package from the specified remove_path 637*9c5db199SXin Li pkg_name : name of the package (ex: test-sleeptest.tar.bz2, 638*9c5db199SXin Li dep-gcc.tar.bz2) 639*9c5db199SXin Li remove_path : the location to remove the package from. 640*9c5db199SXin Li 641*9c5db199SXin Li ''' 642*9c5db199SXin Li if remove_path: 643*9c5db199SXin Li remove_path_list = [remove_path] 644*9c5db199SXin Li elif len(self.upload_paths) > 0: 645*9c5db199SXin Li remove_path_list = self.upload_paths 646*9c5db199SXin Li else: 647*9c5db199SXin Li raise error.PackageRemoveError( 648*9c5db199SXin Li "Invalid path to remove the pkg from") 649*9c5db199SXin Li 650*9c5db199SXin Li checksum_path = self._get_checksum_file_path() 651*9c5db199SXin Li 652*9c5db199SXin Li if remove_checksum: 653*9c5db199SXin Li self.remove_checksum(pkg_name) 654*9c5db199SXin Li 655*9c5db199SXin Li # remove the package and upload the checksum file to the repos 656*9c5db199SXin Li for path in remove_path_list: 657*9c5db199SXin Li self.remove_pkg_file(pkg_name, path) 658*9c5db199SXin Li self.upload_pkg_file(checksum_path, path) 659*9c5db199SXin Li 660*9c5db199SXin Li 661*9c5db199SXin Li def remove_pkg_file(self, filename, pkg_dir): 662*9c5db199SXin Li ''' 663*9c5db199SXin Li Remove the file named filename from pkg_dir 664*9c5db199SXin Li ''' 665*9c5db199SXin Li try: 666*9c5db199SXin Li # Remove the file 667*9c5db199SXin Li if pkg_dir.startswith('ssh://'): 668*9c5db199SXin Li hostline, remote_path = parse_ssh_path(pkg_dir) 669*9c5db199SXin Li path = os.path.join(remote_path, filename) 670*9c5db199SXin Li utils.run("ssh %s 'rm -rf %s/%s'" % (hostline, remote_path, 671*9c5db199SXin Li path)) 672*9c5db199SXin Li else: 673*9c5db199SXin Li os.remove(os.path.join(pkg_dir, filename)) 674*9c5db199SXin Li except (IOError, os.error) as why: 675*9c5db199SXin Li raise error.PackageRemoveError("Could not remove %s from %s: %s " 676*9c5db199SXin Li % (filename, pkg_dir, why)) 677*9c5db199SXin Li 678*9c5db199SXin Li 679*9c5db199SXin Li def get_mirror_list(self, repo_urls): 680*9c5db199SXin Li ''' 681*9c5db199SXin Li Stub function for site specific mirrors. 682*9c5db199SXin Li 683*9c5db199SXin Li Returns: 684*9c5db199SXin Li Priority ordered list 685*9c5db199SXin Li ''' 686*9c5db199SXin Li return repo_urls 687*9c5db199SXin Li 688*9c5db199SXin Li 689*9c5db199SXin Li def _get_checksum_file_path(self): 690*9c5db199SXin Li ''' 691*9c5db199SXin Li Return the complete path of the checksum file (assumed to be stored 692*9c5db199SXin Li in self.pkgmgr_dir 693*9c5db199SXin Li ''' 694*9c5db199SXin Li return os.path.join(self.pkgmgr_dir, CHECKSUM_FILE) 695*9c5db199SXin Li 696*9c5db199SXin Li 697*9c5db199SXin Li def _get_checksum_dict(self): 698*9c5db199SXin Li ''' 699*9c5db199SXin Li Fetch the checksum file if not already fetched. If the checksum file 700*9c5db199SXin Li cannot be fetched from the repos then a new file is created with 701*9c5db199SXin Li the current package's (specified in pkg_path) checksum value in it. 702*9c5db199SXin Li Populate the local checksum dictionary with the values read from 703*9c5db199SXin Li the checksum file. 704*9c5db199SXin Li The checksum file is assumed to be present in self.pkgmgr_dir 705*9c5db199SXin Li ''' 706*9c5db199SXin Li checksum_path = self._get_checksum_file_path() 707*9c5db199SXin Li if not self._checksum_dict: 708*9c5db199SXin Li # Fetch the checksum file 709*9c5db199SXin Li try: 710*9c5db199SXin Li if not self.exists(checksum_path): 711*9c5db199SXin Li # The packages checksum file does not exist locally. 712*9c5db199SXin Li # See if it is present in the repositories. 713*9c5db199SXin Li self.fetch_pkg(CHECKSUM_FILE, checksum_path) 714*9c5db199SXin Li except error.PackageFetchError: 715*9c5db199SXin Li # This should not happen whilst fetching a package..if a 716*9c5db199SXin Li # package is present in the repository, the corresponding 717*9c5db199SXin Li # checksum file should also be automatically present. This 718*9c5db199SXin Li # case happens only when a package 719*9c5db199SXin Li # is being uploaded and if it is the first package to be 720*9c5db199SXin Li # uploaded to the repos (hence no checksum file created yet) 721*9c5db199SXin Li # Return an empty dictionary in that case 722*9c5db199SXin Li return {} 723*9c5db199SXin Li 724*9c5db199SXin Li # Read the checksum file into memory 725*9c5db199SXin Li checksum_file_contents = self._run_command('cat ' 726*9c5db199SXin Li + checksum_path).stdout 727*9c5db199SXin Li 728*9c5db199SXin Li # Return {} if we have an empty checksum file present 729*9c5db199SXin Li if not checksum_file_contents.strip(): 730*9c5db199SXin Li return {} 731*9c5db199SXin Li 732*9c5db199SXin Li # Parse the checksum file contents into self._checksum_dict 733*9c5db199SXin Li for line in checksum_file_contents.splitlines(): 734*9c5db199SXin Li checksum, package_name = line.split(None, 1) 735*9c5db199SXin Li self._checksum_dict[package_name] = checksum 736*9c5db199SXin Li 737*9c5db199SXin Li return self._checksum_dict 738*9c5db199SXin Li 739*9c5db199SXin Li 740*9c5db199SXin Li def _save_checksum_dict(self, checksum_dict): 741*9c5db199SXin Li ''' 742*9c5db199SXin Li Save the checksum dictionary onto the checksum file. Update the 743*9c5db199SXin Li local _checksum_dict variable with this new set of values. 744*9c5db199SXin Li checksum_dict : New checksum dictionary 745*9c5db199SXin Li checksum_dir : The directory in which to store the checksum file to. 746*9c5db199SXin Li ''' 747*9c5db199SXin Li checksum_path = self._get_checksum_file_path() 748*9c5db199SXin Li self._checksum_dict = checksum_dict.copy() 749*9c5db199SXin Li checksum_contents = '\n'.join(checksum + ' ' + pkg_name 750*9c5db199SXin Li for pkg_name, checksum in 751*9c5db199SXin Li six.iteritems(checksum_dict)) 752*9c5db199SXin Li # Write the checksum file back to disk 753*9c5db199SXin Li self._run_command('echo "%s" > %s' % (checksum_contents, 754*9c5db199SXin Li checksum_path), 755*9c5db199SXin Li _run_command_dargs={'verbose': False}) 756*9c5db199SXin Li 757*9c5db199SXin Li 758*9c5db199SXin Li def compute_checksum(self, pkg_path): 759*9c5db199SXin Li ''' 760*9c5db199SXin Li Compute the MD5 checksum for the package file and return it. 761*9c5db199SXin Li pkg_path : The complete path for the package file 762*9c5db199SXin Li ''' 763*9c5db199SXin Li # Check if the checksum has been pre-calculated. 764*9c5db199SXin Li # There are two modes of operation: 765*9c5db199SXin Li # 766*9c5db199SXin Li # 1. Package is compiled on dev machine / build server : In this 767*9c5db199SXin Li # case, we will have the freshest checksum during the install 768*9c5db199SXin Li # phase (which was computed and stored during src_compile). The 769*9c5db199SXin Li # checksum always gets recomputed during src_compile. 770*9c5db199SXin Li # 771*9c5db199SXin Li # 2. Package in installed from a fetched prebuilt: Here, we will 772*9c5db199SXin Li # have the checksum associated with what was used to compile 773*9c5db199SXin Li # the prebuilt. So it is expected to be the same. 774*9c5db199SXin Li checksum_path = pkg_path + '.checksum' 775*9c5db199SXin Li if os.path.exists(checksum_path): 776*9c5db199SXin Li print("Checksum %s exists" % checksum_path) 777*9c5db199SXin Li with open(checksum_path, "r") as f: 778*9c5db199SXin Li return f.read().replace('\n', '') 779*9c5db199SXin Li md5sum_output = self._run_command("md5sum %s " % pkg_path).stdout 780*9c5db199SXin Li return md5sum_output.split()[0] 781*9c5db199SXin Li 782*9c5db199SXin Li 783*9c5db199SXin Li def update_checksum(self, pkg_path): 784*9c5db199SXin Li ''' 785*9c5db199SXin Li Update the checksum of the package in the packages' checksum 786*9c5db199SXin Li file. This method is called whenever a package is fetched just 787*9c5db199SXin Li to be sure that the checksums in the local file are the latest. 788*9c5db199SXin Li pkg_path : The complete path to the package file. 789*9c5db199SXin Li ''' 790*9c5db199SXin Li # Compute the new checksum 791*9c5db199SXin Li new_checksum = self.compute_checksum(pkg_path) 792*9c5db199SXin Li checksum_dict = self._get_checksum_dict() 793*9c5db199SXin Li checksum_dict[os.path.basename(pkg_path)] = new_checksum 794*9c5db199SXin Li self._save_checksum_dict(checksum_dict) 795*9c5db199SXin Li 796*9c5db199SXin Li 797*9c5db199SXin Li def remove_checksum(self, pkg_name): 798*9c5db199SXin Li ''' 799*9c5db199SXin Li Remove the checksum of the package from the packages checksum file. 800*9c5db199SXin Li This method is called whenever a package is removed from the 801*9c5db199SXin Li repositories in order clean its corresponding checksum. 802*9c5db199SXin Li pkg_name : The name of the package to be removed 803*9c5db199SXin Li ''' 804*9c5db199SXin Li checksum_dict = self._get_checksum_dict() 805*9c5db199SXin Li if pkg_name in checksum_dict: 806*9c5db199SXin Li del checksum_dict[pkg_name] 807*9c5db199SXin Li self._save_checksum_dict(checksum_dict) 808*9c5db199SXin Li 809*9c5db199SXin Li 810*9c5db199SXin Li def compare_checksum(self, pkg_path): 811*9c5db199SXin Li ''' 812*9c5db199SXin Li Calculate the checksum of the file specified in pkg_path and 813*9c5db199SXin Li compare it with the checksum in the checksum file 814*9c5db199SXin Li Return True if both match else return False. 815*9c5db199SXin Li pkg_path : The full path to the package file for which the 816*9c5db199SXin Li checksum is being compared 817*9c5db199SXin Li ''' 818*9c5db199SXin Li checksum_dict = self._get_checksum_dict() 819*9c5db199SXin Li package_name = os.path.basename(pkg_path) 820*9c5db199SXin Li if not checksum_dict or package_name not in checksum_dict: 821*9c5db199SXin Li return False 822*9c5db199SXin Li 823*9c5db199SXin Li repository_checksum = checksum_dict[package_name] 824*9c5db199SXin Li local_checksum = self.compute_checksum(pkg_path) 825*9c5db199SXin Li return (local_checksum == repository_checksum) 826*9c5db199SXin Li 827*9c5db199SXin Li 828*9c5db199SXin Li def tar_package(self, pkg_name, src_dir, dest_dir, exclude_string=None): 829*9c5db199SXin Li ''' 830*9c5db199SXin Li Create a tar.bz2 file with the name 'pkg_name' say test-blah.tar.bz2. 831*9c5db199SXin Li Excludes the directories specified in exclude_string while tarring 832*9c5db199SXin Li the source. Returns the tarball path. 833*9c5db199SXin Li ''' 834*9c5db199SXin Li tarball_path = os.path.join(dest_dir, pkg_name) 835*9c5db199SXin Li temp_path = tarball_path + '.tmp' 836*9c5db199SXin Li cmd_list = ['tar', '-cf', temp_path, '-C', src_dir] 837*9c5db199SXin Li if _PBZIP2_AVAILABLE: 838*9c5db199SXin Li cmd_list.append('--use-compress-prog=pbzip2') 839*9c5db199SXin Li else: 840*9c5db199SXin Li cmd_list.append('-j') 841*9c5db199SXin Li if exclude_string is not None: 842*9c5db199SXin Li cmd_list.append(exclude_string) 843*9c5db199SXin Li 844*9c5db199SXin Li try: 845*9c5db199SXin Li utils.system(' '.join(cmd_list)) 846*9c5db199SXin Li except: 847*9c5db199SXin Li os.unlink(temp_path) 848*9c5db199SXin Li raise 849*9c5db199SXin Li 850*9c5db199SXin Li os.rename(temp_path, tarball_path) 851*9c5db199SXin Li return tarball_path 852*9c5db199SXin Li 853*9c5db199SXin Li 854*9c5db199SXin Li def untar_required(self, tarball_path, dest_dir): 855*9c5db199SXin Li ''' 856*9c5db199SXin Li Compare the checksum of the tarball_path with the .checksum file 857*9c5db199SXin Li in the dest_dir and return False if it matches. The untar 858*9c5db199SXin Li of the package happens only if the checksums do not match. 859*9c5db199SXin Li ''' 860*9c5db199SXin Li checksum_path = os.path.join(dest_dir, '.checksum') 861*9c5db199SXin Li try: 862*9c5db199SXin Li existing_checksum = self._run_command('cat ' + checksum_path).stdout 863*9c5db199SXin Li except (error.CmdError, error.AutoservRunError): 864*9c5db199SXin Li # If the .checksum file is not present (generally, this should 865*9c5db199SXin Li # not be the case) then return True so that the untar happens 866*9c5db199SXin Li return True 867*9c5db199SXin Li 868*9c5db199SXin Li new_checksum = self.compute_checksum(tarball_path) 869*9c5db199SXin Li return (new_checksum.strip() != existing_checksum.strip()) 870*9c5db199SXin Li 871*9c5db199SXin Li 872*9c5db199SXin Li def untar_pkg(self, tarball_path, dest_dir): 873*9c5db199SXin Li ''' 874*9c5db199SXin Li Untar the package present in the tarball_path and put a 875*9c5db199SXin Li ".checksum" file in the dest_dir containing the checksum 876*9c5db199SXin Li of the tarball. This method 877*9c5db199SXin Li assumes that the package to be untarred is of the form 878*9c5db199SXin Li <name>.tar.bz2 879*9c5db199SXin Li ''' 880*9c5db199SXin Li self._run_command('tar --no-same-owner -xjf %s -C %s' % 881*9c5db199SXin Li (tarball_path, dest_dir)) 882*9c5db199SXin Li # Put the .checksum file in the install_dir to note 883*9c5db199SXin Li # where the package came from 884*9c5db199SXin Li pkg_checksum = self.compute_checksum(tarball_path) 885*9c5db199SXin Li pkg_checksum_path = os.path.join(dest_dir, 886*9c5db199SXin Li '.checksum') 887*9c5db199SXin Li self._run_command('echo "%s" > %s ' 888*9c5db199SXin Li % (pkg_checksum, pkg_checksum_path)) 889*9c5db199SXin Li 890*9c5db199SXin Li 891*9c5db199SXin Li @staticmethod 892*9c5db199SXin Li def get_tarball_name(name, pkg_type): 893*9c5db199SXin Li """Converts a package name and type into a tarball name. 894*9c5db199SXin Li 895*9c5db199SXin Li @param name: The name of the package 896*9c5db199SXin Li @param pkg_type: The type of the package 897*9c5db199SXin Li 898*9c5db199SXin Li @returns A tarball filename for that specific type of package 899*9c5db199SXin Li """ 900*9c5db199SXin Li assert '-' not in pkg_type 901*9c5db199SXin Li return '%s-%s.tar.bz2' % (pkg_type, name) 902*9c5db199SXin Li 903*9c5db199SXin Li 904*9c5db199SXin Li @staticmethod 905*9c5db199SXin Li def parse_tarball_name(tarball_name): 906*9c5db199SXin Li """Coverts a package tarball name into a package name and type. 907*9c5db199SXin Li 908*9c5db199SXin Li @param tarball_name: The filename of the tarball 909*9c5db199SXin Li 910*9c5db199SXin Li @returns (name, pkg_type) where name is the package name and pkg_type 911*9c5db199SXin Li is the package type. 912*9c5db199SXin Li """ 913*9c5db199SXin Li match = re.search(r'^([^-]*)-(.*)\.tar\.bz2$', tarball_name) 914*9c5db199SXin Li pkg_type, name = match.groups() 915*9c5db199SXin Li return name, pkg_type 916*9c5db199SXin Li 917*9c5db199SXin Li 918*9c5db199SXin Li def is_url(self, url): 919*9c5db199SXin Li """Return true if path looks like a URL""" 920*9c5db199SXin Li return url.startswith('http://') 921*9c5db199SXin Li 922*9c5db199SXin Li 923*9c5db199SXin Li def get_package_name(self, url, pkg_type): 924*9c5db199SXin Li ''' 925*9c5db199SXin Li Extract the group and test name for the url. This method is currently 926*9c5db199SXin Li used only for tests. 927*9c5db199SXin Li ''' 928*9c5db199SXin Li if pkg_type == 'test': 929*9c5db199SXin Li regex = '[^:]+://(.*)/([^/]*)$' 930*9c5db199SXin Li return self._get_package_name(url, regex) 931*9c5db199SXin Li else: 932*9c5db199SXin Li return ('', url) 933*9c5db199SXin Li 934*9c5db199SXin Li 935*9c5db199SXin Li def _get_package_name(self, url, regex): 936*9c5db199SXin Li if not self.is_url(url): 937*9c5db199SXin Li if url.endswith('.tar.bz2'): 938*9c5db199SXin Li testname = url.replace('.tar.bz2', '') 939*9c5db199SXin Li testname = re.sub(r'(\d*)\.', '', testname) 940*9c5db199SXin Li return (testname, testname) 941*9c5db199SXin Li else: 942*9c5db199SXin Li return ('', url) 943*9c5db199SXin Li 944*9c5db199SXin Li match = re.match(regex, url) 945*9c5db199SXin Li if not match: 946*9c5db199SXin Li return ('', url) 947*9c5db199SXin Li group, filename = match.groups() 948*9c5db199SXin Li # Generate the group prefix. 949*9c5db199SXin Li group = re.sub(r'\W', '_', group) 950*9c5db199SXin Li # Drop the extension to get the raw test name. 951*9c5db199SXin Li testname = re.sub(r'\.tar\.bz2', '', filename) 952*9c5db199SXin Li # Drop any random numbers at the end of the test name if any 953*9c5db199SXin Li testname = re.sub(r'\.(\d*)', '', testname) 954*9c5db199SXin Li return (group, testname) 955*9c5db199SXin Li 956*9c5db199SXin Li 957*9c5db199SXin Liclass SiteHttpFetcher(HttpFetcher): 958*9c5db199SXin Li curl_cmd_pattern = ('curl --connect-timeout 15 --retry 5 ' 959*9c5db199SXin Li '--retry-delay 5 --fail -s %s -o %s') 960*9c5db199SXin Li 961*9c5db199SXin Li # shortcut quick http test for now since our dev server does not support 962*9c5db199SXin Li # this operation. 963*9c5db199SXin Li def _quick_http_test(self): 964*9c5db199SXin Li return 965*9c5db199SXin Li 966*9c5db199SXin Li 967*9c5db199SXin Liclass PackageManager(BasePackageManager): 968*9c5db199SXin Li def get_fetcher(self, url): 969*9c5db199SXin Li if url.startswith('http://'): 970*9c5db199SXin Li return SiteHttpFetcher(self, url) 971*9c5db199SXin Li else: 972*9c5db199SXin Li return super(PackageManager, self).get_fetcher(url) 973