Home | History | Annotate | Download | only in swarming_client
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 
      6 # TODO(borenet): This module was copied from build.git and heavily modified to
      7 # remove dependencies on other modules in build.git.  It belongs in a different
      8 # repo. Remove this once it has been moved.
      9 
     10 
     11 from recipe_engine import recipe_api
     12 
     13 
     14 class SwarmingClientApi(recipe_api.RecipeApi):
     15   """Code that both isolate and swarming recipe modules depend on.
     16 
     17   Both swarming and isolate scripts live in a single repository called
     18   'swarming client'. This module include common functionality like finding
     19   existing swarming client checkout, fetching a new one, getting version of
     20   a swarming script, etc.
     21   """
     22 
     23   def __init__(self, **kwargs):
     24     super(SwarmingClientApi, self).__init__(**kwargs)
     25     self._client_path = None
     26     self._script_version = {}
     27 
     28   def checkout(self, revision=None, curl_trace_file=None, can_fail_build=True):
     29     """Returns a step to checkout swarming client into a separate directory.
     30 
     31     Ordinarily swarming client is checked out via Chromium DEPS into
     32     src/tools/swarming_client. This step configures recipe module to use
     33     a separate checkout.
     34 
     35     If |revision| is None, this requires the build property
     36     'parent_got_swarming_client_revision' to be present, and raises an exception
     37     otherwise. Fail-fast behavior is used because if machines silently fell back
     38     to checking out the entire workspace, that would cause dramatic increases
     39     in cycle time if a misconfiguration were made and it were no longer possible
     40     for the bot to check out swarming_client separately.
     41     """
     42     # If the following line throws an exception, it either means the
     43     # bot is misconfigured, or, if you're testing locally, that you
     44     # need to pass in some recent legal revision for this property.
     45     if revision is None:
     46       revision = self.m.properties['parent_got_swarming_client_revision']
     47     self._client_path = self.m.path['start_dir'].join('swarming.client')
     48     self.m.git.checkout(
     49         url='https://chromium.googlesource.com/external/swarming.client.git',
     50         ref=revision,
     51         dir_path=self._client_path,
     52         step_suffix='swarming_client',
     53         curl_trace_file=curl_trace_file,
     54         can_fail_build=can_fail_build)
     55 
     56   @property
     57   def path(self):
     58     """Returns path to a swarming client checkout.
     59 
     60     It's subdirectory of Chromium src/ checkout or a separate directory if
     61     'checkout_swarming_client' step was used.
     62     """
     63     if self._client_path:
     64       return self._client_path
     65     # Default is swarming client path in chromium src/ checkout.
     66     # TODO(vadimsh): This line assumes the recipe is working with
     67     # Chromium checkout.
     68     return self.m.path['checkout'].join('tools', 'swarming_client')
     69 
     70   def query_script_version(self, script, step_test_data=None):
     71     """Yields a step to query a swarming script for its version.
     72 
     73     Version tuple is later accessible via 'get_script_version' method. If
     74     |step_test_data| is given, it is a tuple with version to use in expectation
     75     tests by default.
     76 
     77     Does nothing if script's version is already known.
     78     """
     79     # Convert |step_test_data| from tuple of ints back to a version string.
     80     if step_test_data:
     81       assert isinstance(step_test_data, tuple)
     82       assert all(isinstance(x, int) for x in step_test_data)
     83       as_text = '.'.join(map(str, step_test_data))
     84       step_test_data_cb = lambda: self.m.raw_io.test_api.stream_output(as_text)
     85     else:
     86       step_test_data_cb = None
     87 
     88     if script not in self._script_version:
     89       try:
     90         self.m.python(
     91           name='%s --version' % script,
     92           script=self.path.join(script),
     93           args=['--version'],
     94           stdout=self.m.raw_io.output_text(),
     95           step_test_data=step_test_data_cb)
     96       finally:
     97         step_result = self.m.step.active_result
     98         version = step_result.stdout.strip()
     99         step_result.presentation.step_text = version
    100         self._script_version[script] = tuple(map(int, version.split('.')))
    101 
    102       return step_result
    103 
    104   def get_script_version(self, script):
    105     """Returns a version of some swarming script as a tuple (Major, Minor, Rev).
    106 
    107     It should have been queried by 'query_script_version' step before. Raises
    108     AssertionError if it wasn't.
    109     """
    110     assert script in self._script_version, script
    111     return self._script_version[script]
    112 
    113   def ensure_script_version(self, script, min_version, step_test_data=None):
    114     """Yields steps to ensure a script version is not older than |min_version|.
    115 
    116     Will abort recipe execution if it is.
    117     """
    118     step_result = self.query_script_version(
    119         script, step_test_data=step_test_data or min_version)
    120     version = self.get_script_version(script)
    121     if version < min_version:
    122       expecting = '.'.join(map(str, min_version))
    123       got = '.'.join(map(str, version))
    124       abort_reason = 'Expecting at least v%s, got v%s' % (expecting, got)
    125 
    126       # TODO(martiniss) remove once recipe 1.5 migration done
    127       step_result = self.m.python.inline(
    128           '%s is too old' % script,
    129           'import sys; sys.exit(1)',
    130           add_python_log=False)
    131       # TODO(martiniss) get rid of this bare string.
    132       step_result.presentation.status = self.m.step.FAILURE
    133       step_result.presentation.step_text = abort_reason
    134 
    135       raise self.m.step.StepFailure(abort_reason)
    136