| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Service for tracking isolates and looking them up by builder and commit. |
| |
| An isolate is a way to describe the dependencies of a specific build. |
| |
| More about isolates: |
| https://github.com/luci/luci-py/blob/master/appengine/isolate/doc/client/Design.md |
| """ |
| |
| import json |
| |
| from dashboard.api import api_request_handler |
| from dashboard.common import utils |
| from dashboard.pinpoint.models import change as change_module |
| from dashboard.pinpoint.models import isolate |
| |
| |
| class Isolate(api_request_handler.ApiRequestHandler): |
| """Handler for managing isolates. |
| |
| A post request adds new isolate information. |
| A get request looks up an isolate hash from the builder, commit, and target. |
| """ |
| |
| def get(self): |
| """Look up an isolate hash. |
| |
| Args: |
| builder_name: The name of the builder that produced the isolate. |
| change: The Change the isolate is for, as a JSON string. |
| target: The isolate target. |
| """ |
| # Get parameters. |
| parameters = ( |
| ('builder_name', str), |
| ('change', lambda x: change_module.Change.FromDict(json.loads(x))), |
| ('target', str), |
| ) |
| try: |
| # pylint: disable=unbalanced-tuple-unpacking |
| builder_name, change, target = self._ValidateParameters(parameters) |
| except (KeyError, TypeError, ValueError) as e: |
| self.response.set_status(400) |
| self.response.write(e) |
| return |
| |
| # Get. |
| try: |
| isolate_server, isolate_hash = isolate.Get(builder_name, change, target) |
| except KeyError as e: |
| self.response.set_status(404) |
| self.response.write(e) |
| return |
| |
| self.response.write(json.dumps({ |
| 'isolate_server': isolate_server, |
| 'isolate_hash': isolate_hash, |
| })) |
| |
| def _CheckUser(self): |
| # TODO: Remove when all Pinpoint builders are migrated to LUCI. |
| if self.request.remote_addr in utils.GetIpWhitelist(): |
| return |
| self._CheckIsInternalUser() |
| |
| def Post(self): |
| """Add new isolate information. |
| |
| Args: |
| builder_name: The name of the builder that produced the isolate. |
| change: The Change the isolate is for, as a JSON string. |
| isolate_server: The hostname of the server where the isolates are stored. |
| isolate_map: A JSON dict mapping the target names to the isolate hashes. |
| """ |
| # Get parameters. |
| parameters = ( |
| ('builder_name', str), |
| ('change', lambda x: change_module.Change.FromDict(json.loads(x))), |
| ('isolate_server', str), |
| ('isolate_map', json.loads), |
| ) |
| try: |
| # pylint: disable=unbalanced-tuple-unpacking |
| builder_name, change, isolate_server, isolate_map = ( |
| self._ValidateParameters(parameters)) |
| except (KeyError, TypeError, ValueError) as e: |
| self.response.set_status(400) |
| self.response.write(json.dumps({'error': e.message})) |
| return |
| |
| # Put information into the datastore. |
| isolate_infos = [ |
| (builder_name, change, target, isolate_server, isolate_hash) |
| for target, isolate_hash in isolate_map.iteritems()] |
| isolate.Put(isolate_infos) |
| |
| # Respond to the API user. |
| self.response.write(json.dumps(isolate_infos)) |
| |
| def _ValidateParameters(self, parameters): |
| """Ensure the right parameters are present and valid. |
| |
| Args: |
| parameters: Iterable of (name, converter) tuples where name is the |
| parameter name and converter is a function used to validate |
| and convert that parameter into its internal representation. |
| |
| Returns: |
| A list of parsed parameter values. |
| |
| Raises: |
| TypeError: The wrong parameters are present. |
| ValueError: The parameters have invalid values. |
| """ |
| parameter_names = tuple(parameter_name for parameter_name, _ in parameters) |
| for given_parameter in self.request.params: |
| if given_parameter not in parameter_names: |
| raise TypeError('Unknown parameter: %s' % given_parameter) |
| |
| parameter_values = [] |
| |
| for parameter_name, parameter_converter in parameters: |
| if parameter_name not in self.request.params: |
| raise TypeError('Missing parameter: %s' % parameter_name) |
| |
| parameter_value = self.request.get(parameter_name) |
| if not parameter_value: |
| raise ValueError('Empty parameter: %s' % parameter_name) |
| |
| parameter_value = parameter_converter(parameter_value) |
| |
| parameter_values.append(parameter_value) |
| |
| return parameter_values |