Home | History | Annotate | Download | only in utils
      1 #!/usr/bin/python -u
      2 
      3 """
      4 Utility to upload or remove the packages from the packages repository.
      5 """
      6 
      7 import logging, optparse, os, shutil, sys, tempfile
      8 import common
      9 from autotest_lib.client.common_lib import utils as client_utils
     10 from autotest_lib.client.common_lib import global_config, error
     11 from autotest_lib.client.common_lib import packages
     12 from autotest_lib.server import utils as server_utils
     13 
     14 c = global_config.global_config
     15 logging.basicConfig(level=logging.DEBUG)
     16 
     17 ACTION_REMOVE = 'remove'
     18 ACTION_UPLOAD = 'upload'
     19 ACTION_TAR_ONLY = 'tar_only'
     20 
     21 def get_exclude_string(client_dir):
     22     '''
     23     Get the exclude string for the tar command to exclude specific
     24     subdirectories inside client_dir.
     25     For profilers we need to exclude everything except the __init__.py
     26     file so that the profilers can be imported.
     27     '''
     28     exclude_string = ('--exclude="deps/*" --exclude="tests/*" '
     29                       '--exclude="site_tests/*" --exclude="**.pyc"')
     30 
     31     # Get the profilers directory
     32     prof_dir = os.path.join(client_dir, 'profilers')
     33 
     34     # Include the __init__.py file for the profilers and exclude all its
     35     # subdirectories
     36     for f in os.listdir(prof_dir):
     37         if os.path.isdir(os.path.join(prof_dir, f)):
     38             exclude_string += ' --exclude="profilers/%s"' % f
     39 
     40     # The '.' here is needed to zip the files in the current
     41     # directory. We use '-C' for tar to change to the required
     42     # directory i.e. src_dir and then zip up the files in that
     43     # directory(which is '.') excluding the ones in the exclude_dirs
     44     exclude_string += " ."
     45 
     46     # TODO(milleral): This is sad and ugly.  http://crbug.com/258161
     47     # Surprisingly, |exclude_string| actually means argument list, and
     48     # we'd like to package up the current global_config.ini also, so let's
     49     # just tack it on here.
     50     # Also note that this only works because tar prevents us from un-tarring
     51     # files into parent directories.
     52     exclude_string += " ../global_config.ini"
     53 
     54     return exclude_string
     55 
     56 
     57 def parse_args():
     58     parser = optparse.OptionParser()
     59     parser.add_option("-d", "--dependency", help="package the dependency"
     60                       " from client/deps directory and upload to the repo",
     61                       dest="dep")
     62     parser.add_option("-p", "--profiler", help="package the profiler "
     63                       "from client/profilers directory and upload to the repo",
     64                       dest="prof")
     65     parser.add_option("-t", "--test", help="package the test from client/tests"
     66                       " or client/site_tests and upload to the repo.",
     67                       dest="test")
     68     parser.add_option("-c", "--client", help="package the client "
     69                       "directory alone without the tests, deps and profilers",
     70                       dest="client", action="store_true", default=False)
     71     parser.add_option("-f", "--file", help="simply uploads the specified"
     72                       "file on to the repo", dest="file")
     73     parser.add_option("-r", "--repository", help="the URL of the packages"
     74                       "repository location to upload the packages to.",
     75                       dest="repo", default=None)
     76     parser.add_option("-o", "--output_dir", help="the output directory"
     77                       "to place tarballs and md5sum files in.",
     78                       dest="output_dir", default=None)
     79     parser.add_option("-a", "--action", help="the action to perform",
     80                       dest="action", choices=(ACTION_UPLOAD, ACTION_REMOVE,
     81                                               ACTION_TAR_ONLY), default=None)
     82     parser.add_option("--all", help="Upload all the files locally "
     83                       "to all the repos specified in global_config.ini. "
     84                       "(includes the client, tests, deps and profilers)",
     85                       dest="all", action="store_true", default=False)
     86 
     87     options, args = parser.parse_args()
     88     return options, args
     89 
     90 def get_build_dir(name, dest_dir, pkg_type):
     91     """Method to generate the build directory where the tarball and checksum
     92     is stored. The following package types are handled: test, dep, profiler.
     93     Package type 'client' is not handled.
     94     """
     95     if pkg_type == 'client':
     96         # NOTE: The "tar_only" action for pkg_type "client" has no use
     97         # case yet. No known invocations of packager.py with
     98         # --action=tar_only send in clients in the command line. Please
     99         # confirm the behaviour is expected before this type is enabled for
    100         # "tar_only" actions.
    101         print ('Tar action not supported for pkg_type= %s, name = %s' %
    102                 pkg_type, name)
    103         return None
    104     # For all packages, the work-dir should have 'client' appended to it.
    105     base_build_dir = os.path.join(dest_dir, 'client')
    106     if pkg_type == 'test':
    107         build_dir = os.path.join(get_test_dir(name, base_build_dir), name)
    108     else:
    109         # For profiler and dep, we append 's', and then append the name.
    110         # TODO(pmalani): Make this less fiddly?
    111         build_dir = os.path.join(base_build_dir, pkg_type + 's', name)
    112     return build_dir
    113 
    114 def process_packages(pkgmgr, pkg_type, pkg_names, src_dir,
    115                      action, dest_dir=None):
    116     """Method to upload or remove package depending on the flag passed to it.
    117 
    118     If tar_only is set to True, this routine is solely used to generate a
    119     tarball and compute the md5sum from that tarball.
    120     If the tar_only flag is True, then the remove flag is ignored.
    121     """
    122     exclude_string = ' .'
    123     names = [p.strip() for p in pkg_names.split(',')]
    124     for name in names:
    125         print "process_packages: Processing %s ... " % name
    126         if pkg_type == 'client':
    127             pkg_dir = src_dir
    128             exclude_string = get_exclude_string(pkg_dir)
    129         elif pkg_type == 'test':
    130             # if the package is a test then look whether it is in client/tests
    131             # or client/site_tests
    132             pkg_dir = os.path.join(get_test_dir(name, src_dir), name)
    133         else:
    134             # for the profilers and deps
    135             pkg_dir = os.path.join(src_dir, name)
    136 
    137         pkg_name = pkgmgr.get_tarball_name(name, pkg_type)
    138 
    139         exclude_string_tar = ((
    140             ' --exclude="**%s" --exclude="**%s.checksum" ' %
    141             (pkg_name, pkg_name)) + exclude_string)
    142         if action == ACTION_TAR_ONLY:
    143             # We don't want any pre-existing tarballs and checksums to
    144             # be repackaged, so we should purge these.
    145             build_dir = get_build_dir(name, dest_dir, pkg_type)
    146             try:
    147                 packages.check_diskspace(build_dir)
    148             except error.RepoDiskFullError as e:
    149                 msg = ("Work_dir directory for packages %s does not have "
    150                        "enough space available: %s" % (build_dir, e))
    151                 raise error.RepoDiskFullError(msg)
    152             tarball_path = pkgmgr.tar_package(pkg_name, pkg_dir,
    153                                               build_dir, exclude_string_tar)
    154 
    155             # Create the md5 hash too.
    156             md5sum = pkgmgr.compute_checksum(tarball_path)
    157             md5sum_filepath = os.path.join(build_dir, pkg_name + '.checksum')
    158             with open(md5sum_filepath, "w") as f:
    159                 f.write(md5sum)
    160 
    161         elif action == ACTION_UPLOAD:
    162             # Tar the source and upload
    163             temp_dir = tempfile.mkdtemp()
    164             try:
    165                 try:
    166                     packages.check_diskspace(temp_dir)
    167                 except error.RepoDiskFullError, e:
    168                     msg = ("Temporary directory for packages %s does not have "
    169                            "enough space available: %s" % (temp_dir, e))
    170                     raise error.RepoDiskFullError(msg)
    171 
    172                 # Check if tarball already exists. If it does, then don't
    173                 # create a tarball again.
    174                 tarball_path = os.path.join(pkg_dir, pkg_name);
    175                 if os.path.exists(tarball_path):
    176                     print("process_packages: Tarball %s already exists" %
    177                           tarball_path)
    178                 else:
    179                     tarball_path = pkgmgr.tar_package(pkg_name, pkg_dir,
    180                                                       temp_dir,
    181                                                       exclude_string_tar)
    182                 # Compare the checksum with what packages.checksum has. If they
    183                 # match then we don't need to perform the upload.
    184                 if not pkgmgr.compare_checksum(tarball_path):
    185                     pkgmgr.upload_pkg(tarball_path, update_checksum=True)
    186                 else:
    187                     logging.warning('Checksum not changed for %s, not copied '
    188                                     'in packages/ directory.', tarball_path)
    189             finally:
    190                 # remove the temporary directory
    191                 shutil.rmtree(temp_dir)
    192         elif action == ACTION_REMOVE:
    193             pkgmgr.remove_pkg(pkg_name, remove_checksum=True)
    194         print "Done."
    195 
    196 
    197 def tar_packages(pkgmgr, pkg_type, pkg_names, src_dir, temp_dir):
    198     """Tar all packages up and return a list of each tar created"""
    199     tarballs = []
    200     exclude_string = ' .'
    201     names = [p.strip() for p in pkg_names.split(',')]
    202     for name in names:
    203         print "tar_packages: Processing %s ... " % name
    204         if pkg_type == 'client':
    205             pkg_dir = src_dir
    206             exclude_string = get_exclude_string(pkg_dir)
    207         elif pkg_type == 'test':
    208             # if the package is a test then look whether it is in client/tests
    209             # or client/site_tests
    210             pkg_dir = os.path.join(get_test_dir(name, src_dir), name)
    211         else:
    212             # for the profilers and deps
    213             pkg_dir = os.path.join(src_dir, name)
    214 
    215         pkg_name = pkgmgr.get_tarball_name(name, pkg_type)
    216 
    217         # We don't want any pre-existing tarballs and checksums to
    218         # be repackaged, so we should purge these.
    219         exclude_string_tar = ((
    220             ' --exclude="**%s" --exclude="**%s.checksum" ' %
    221             (pkg_name, pkg_name)) + exclude_string)
    222         # Check if tarball already exists. If it does, don't duplicate
    223         # the effort.
    224         tarball_path = os.path.join(pkg_dir, pkg_name);
    225         if os.path.exists(tarball_path):
    226           print("tar_packages: Tarball %s already exists" % tarball_path);
    227         else:
    228             tarball_path = pkgmgr.tar_package(pkg_name, pkg_dir,
    229                                               temp_dir, exclude_string_tar)
    230         tarballs.append(tarball_path)
    231     return tarballs
    232 
    233 
    234 def process_all_packages(pkgmgr, client_dir, action):
    235     """Process a full upload of packages as a directory upload."""
    236     dep_dir = os.path.join(client_dir, "deps")
    237     prof_dir = os.path.join(client_dir, "profilers")
    238     # Directory where all are kept
    239     temp_dir = tempfile.mkdtemp()
    240     try:
    241         packages.check_diskspace(temp_dir)
    242     except error.RepoDiskFullError, e:
    243         print ("Temp destination for packages is full %s, aborting upload: %s"
    244                % (temp_dir, e))
    245         os.rmdir(temp_dir)
    246         sys.exit(1)
    247 
    248     # process tests
    249     tests_list = get_subdir_list('tests', client_dir)
    250     tests = ','.join(tests_list)
    251 
    252     # process site_tests
    253     site_tests_list = get_subdir_list('site_tests', client_dir)
    254     site_tests = ','.join(site_tests_list)
    255 
    256     # process deps
    257     deps_list = get_subdir_list('deps', client_dir)
    258     deps = ','.join(deps_list)
    259 
    260     # process profilers
    261     profilers_list = get_subdir_list('profilers', client_dir)
    262     profilers = ','.join(profilers_list)
    263 
    264     # Update md5sum
    265     if action == ACTION_UPLOAD:
    266         all_packages = []
    267         all_packages.extend(tar_packages(pkgmgr, 'profiler', profilers,
    268                                          prof_dir, temp_dir))
    269         all_packages.extend(tar_packages(pkgmgr, 'dep', deps, dep_dir,
    270                                          temp_dir))
    271         all_packages.extend(tar_packages(pkgmgr, 'test', site_tests,
    272                                          client_dir, temp_dir))
    273         all_packages.extend(tar_packages(pkgmgr, 'test', tests, client_dir,
    274                                          temp_dir))
    275         all_packages.extend(tar_packages(pkgmgr, 'client', 'autotest',
    276                                          client_dir, temp_dir))
    277         for package in all_packages:
    278             pkgmgr.upload_pkg(package, update_checksum=True)
    279         client_utils.run('rm -rf ' + temp_dir)
    280     elif action == ACTION_REMOVE:
    281         process_packages(pkgmgr, 'test', tests, client_dir, action=action)
    282         process_packages(pkgmgr, 'test', site_tests, client_dir, action=action)
    283         process_packages(pkgmgr, 'client', 'autotest', client_dir,
    284                          action=action)
    285         process_packages(pkgmgr, 'dep', deps, dep_dir, action=action)
    286         process_packages(pkgmgr, 'profiler', profilers, prof_dir,
    287                          action=action)
    288 
    289 
    290 # Get the list of sub directories present in a directory
    291 def get_subdir_list(name, client_dir):
    292     dir_name = os.path.join(client_dir, name)
    293     return [f for f in
    294             os.listdir(dir_name)
    295             if os.path.isdir(os.path.join(dir_name, f)) ]
    296 
    297 
    298 # Look whether the test is present in client/tests and client/site_tests dirs
    299 def get_test_dir(name, client_dir):
    300     names_test = os.listdir(os.path.join(client_dir, 'tests'))
    301     names_site_test = os.listdir(os.path.join(client_dir, 'site_tests'))
    302     if name in names_test:
    303         src_dir = os.path.join(client_dir, 'tests')
    304     elif name in names_site_test:
    305         src_dir = os.path.join(client_dir, 'site_tests')
    306     else:
    307         print "Test %s not found" % name
    308         sys.exit(0)
    309     return src_dir
    310 
    311 
    312 def main():
    313     # get options and args
    314     options, args = parse_args()
    315 
    316     server_dir = server_utils.get_server_dir()
    317     autotest_dir = os.path.abspath(os.path.join(server_dir, '..'))
    318 
    319     # extract the pkg locations from global config
    320     repo_urls = c.get_config_value('PACKAGES', 'fetch_location',
    321                                    type=list, default=[])
    322     upload_paths = c.get_config_value('PACKAGES', 'upload_location',
    323                                       type=list, default=[])
    324 
    325     if options.repo:
    326         upload_paths.append(options.repo)
    327 
    328     # Having no upload paths basically means you're not using packaging.
    329     if not upload_paths:
    330         print("No upload locations found. Please set upload_location under"
    331               " PACKAGES in the global_config.ini or provide a location using"
    332               " the --repository option.")
    333         return
    334 
    335     client_dir = os.path.join(autotest_dir, "client")
    336 
    337     # Bail out if the client directory does not exist
    338     if not os.path.exists(client_dir):
    339         sys.exit(0)
    340 
    341     dep_dir = os.path.join(client_dir, "deps")
    342     prof_dir = os.path.join(client_dir, "profilers")
    343 
    344     # Due to the delayed uprev-ing of certain ebuilds, we need to support
    345     # both the legacy command line and the new one.
    346     # So, if the new "action" option isn't specified, try looking for the
    347     # old style remove/upload argument
    348     if options.action is None:
    349         if len(args) == 0 or args[0] not in ['upload', 'remove']:
    350             print("Either 'upload' or 'remove' needs to be specified "
    351                   "for the package")
    352             sys.exit(0)
    353         cur_action = args[0]
    354     else:
    355         cur_action = options.action
    356 
    357     if cur_action == ACTION_TAR_ONLY and options.output_dir is None:
    358         print("An output dir has to be specified")
    359         sys.exit(0)
    360 
    361     pkgmgr = packages.PackageManager(autotest_dir, repo_urls=repo_urls,
    362                                      upload_paths=upload_paths,
    363                                      run_function_dargs={'timeout':600})
    364 
    365     if options.all:
    366         process_all_packages(pkgmgr, client_dir, action=cur_action)
    367 
    368     if options.client:
    369         process_packages(pkgmgr, 'client', 'autotest', client_dir,
    370                          action=cur_action)
    371 
    372     if options.dep:
    373         process_packages(pkgmgr, 'dep', options.dep, dep_dir,
    374                          action=cur_action, dest_dir=options.output_dir)
    375 
    376     if options.test:
    377         process_packages(pkgmgr, 'test', options.test, client_dir,
    378                          action=cur_action, dest_dir=options.output_dir)
    379 
    380     if options.prof:
    381         process_packages(pkgmgr, 'profiler', options.prof, prof_dir,
    382                          action=cur_action, dest_dir=options.output_dir)
    383 
    384     if options.file:
    385         if cur_action == ACTION_REMOVE:
    386             pkgmgr.remove_pkg(options.file, remove_checksum=True)
    387         elif cur_action == ACTION_UPLOAD:
    388             pkgmgr.upload_pkg(options.file, update_checksum=True)
    389 
    390 
    391 if __name__ == "__main__":
    392     main()
    393