xref: /aosp_15_r20/external/autotest/client/common_lib/packages.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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