blob: 815b3a1ead32ef3860303d115c9a58eb16001729 [file] [log] [blame]
Xinan Lin3ba18a02019-08-13 15:44:55 -07001# Copyright 2019 The Chromium OS Authors. All rights reserved.
Xinan Linc61196b2019-08-13 10:37:30 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Module for interacting with Buildbucket."""
Xinan Linc61196b2019-08-13 10:37:30 -07006
Xinan Lin3ba18a02019-08-13 15:44:55 -07007import datetime
8import re
9import string
10
11import build_lib
Xinan Linc61196b2019-08-13 10:37:30 -070012import constants
13import file_getter
14
15from chromite.api.gen.test_platform import request_pb2
Xinan Linc61196b2019-08-13 10:37:30 -070016from components import auth
Xinan Lin3ba18a02019-08-13 15:44:55 -070017from components.prpc import client as prpc_client
18from infra_libs.buildbucket.proto import rpc_pb2, build_pb2
Xinan Linc61196b2019-08-13 10:37:30 -070019from infra_libs.buildbucket.proto.rpc_prpc_pb2 import BuildsServiceDescription
20
21from oauth2client import service_account
Xinan Lin3ba18a02019-08-13 15:44:55 -070022
23from google.protobuf import json_format, struct_pb2
Xinan Linc61196b2019-08-13 10:37:30 -070024
25
Xinan Lin3ba18a02019-08-13 15:44:55 -070026_enum = request_pb2.Request.Params.Scheduling
Xinan Linc61196b2019-08-13 10:37:30 -070027NONSTANDARD_POOL_NAMES = {
Xinan Lin3ba18a02019-08-13 15:44:55 -070028 'cq': _enum.MANAGED_POOL_CQ,
29 'bvt': _enum.MANAGED_POOL_BVT,
30 'suites': _enum.MANAGED_POOL_SUITES,
31 'cts': _enum.MANAGED_POOL_CTS,
32 'cts-perbuild': _enum.MANAGED_POOL_CTS_PERBUILD,
33 'continuous': _enum.MANAGED_POOL_CONTINUOUS,
34 'arc-presubmit': _enum.MANAGED_POOL_ARC_PRESUBMIT,
35 'quota': _enum.MANAGED_POOL_QUOTA,
Xinan Linc61196b2019-08-13 10:37:30 -070036}
37
Xinan Lin3ba18a02019-08-13 15:44:55 -070038
Xinan Linc61196b2019-08-13 10:37:30 -070039def _get_client(address):
40 """Create a prpc client instance for given address."""
Xinan Lin3ba18a02019-08-13 15:44:55 -070041
Xinan Linc61196b2019-08-13 10:37:30 -070042 return prpc_client.Client(address, BuildsServiceDescription)
43
44
Xinan Lin3ba18a02019-08-13 15:44:55 -070045class BuildbucketRunError(Exception):
46 """Raised when interactions with buildbucket server fail."""
47
48
Xinan Linc61196b2019-08-13 10:37:30 -070049class TestPlatformClient(object):
50 """prpc client for cros_test_platform, aka frontdoor."""
51
52 def __init__(self, address, project, bucket, builder):
53 self.client = _get_client(address)
54 self.builder = build_pb2.BuilderID(project=project,
55 bucket=bucket,
56 builder=builder)
57 self.scope = 'https://www.googleapis.com/auth/userinfo.email'
58 self.running_env = constants.environment()
59
Xinan Lin3ba18a02019-08-13 15:44:55 -070060 # This is cloned from swarming_lib.py.
61 def _get_final_build(self, **kwargs):
62 """Get the final build to kick off on a lab DUT.
63
64 Args:
65 **kwargs: The parameters of a task loaded from suite queue.
66
67 Returns:
68 A string representing the build to run.
69 """
70 cros_build = kwargs[build_lib.BuildVersionKey.CROS_VERSION]
71 android_build = kwargs[build_lib.BuildVersionKey.ANDROID_BUILD_VERSION]
72 testbed_build = kwargs[build_lib.BuildVersionKey.TESTBED_BUILD_VERSION]
73 return cros_build or android_build or testbed_build
74
75 def _get_scheduling(self, pool, quota):
76 """Assign appropriate quota or pool name to scheduling instance.
77
78 Args:
79 pool: string pool name (e.g. 'bvt', 'quota').
80 quota: string quota account, or None.
81
82 Returns:
83 scheduling: A request_pb2.Request.Params.Scheduling instance.
84 """
85 if quota:
86 return request_pb2.Request.Params.Scheduling(quota_account=quota)
87
88 mp = request_pb2.Request.Params.Scheduling.ManagedPool
89 if mp.DESCRIPTOR.values_by_name.get(pool) is not None:
90 return request_pb2.Request.Params.Scheduling(managed_pool=mp.Value(pool))
91
92 DUT_POOL_PREFIX = r'DUT_POOL_(?P<munged_pool>.+)'
93 match = re.match(DUT_POOL_PREFIX, pool)
94 if match:
95 munged_pool = string.lower(match.group('munged_pool'))
96 if NONSTANDARD_POOL_NAMES.get(munged_pool):
97 return request_pb2.Request.Params.Scheduling(
98 managed_pool=NONSTANDARD_POOL_NAMES.get(munged_pool))
99
100 return request_pb2.Request.Params.Scheduling(unmanaged_pool=pool)
101
102 def _form_test_platform_request(self, final_build, **task_params):
103 """Generate ScheduleBuildRequest for calling buildbucket.
104
105 Args:
106 final_build: string, the build to run with the suite (e.g.
107 'nyan_blaze-release/R69-10895.33.0').
108 **task_params: dict, containing the parameters of a task got from suite
109 queue.
110
111 Returns:
112 A request_pb2 instance.
113 """
114
115 pool = task_params.get('override_pool') or task_params['pool']
116 quota = task_params.get('override_qs_account')
117 scheduling = self._get_scheduling(pool, quota)
118
119 software_dep = request_pb2.Request.Params.SoftwareDependency()
120 software_dep.chromeos_build = final_build
121
122 software_attributes = request_pb2.Request.Params.SoftwareAttributes()
123 software_attributes.build_target.name = task_params['board']
124
125 metadata = request_pb2.Request.Params.Metadata()
126 metadata.test_metadata_url = task_params['test_source_build']
127
128 timeout_mins = int(task_params['timeout_mins'])
129 if task_params.get('timeout'):
130 timeout_mins = int(task_params['timeout'])
131 if timeout_mins > constants.Buildbucket.MAX_SWARMING_TIMEOUT_MINS:
132 timeout_mins = constants.Buildbucket.MAX_SWARMING_TIMEOUT_MINS
133 td = datetime.timedelta(minutes=timeout_mins)
134 params_time = request_pb2.Request.Params.Time()
135 params_time.maximum_duration.FromTimedelta(td)
136
137 params = request_pb2.Request.Params(scheduling=scheduling,
138 software_dependencies=[software_dep],
139 software_attributes=software_attributes,
140 metadata=metadata,
141 time=params_time)
142
143 if task_params['model'] != 'None':
144 params.hardware_attributes.model = task_params['model']
145
146 if task_params['job_retry'] == 'True':
147 params.retry.allow = True
148 params.retry.max = constants.Buildbucket.MAX_RETRY
149
150 fw_rw_build = task_params.get(build_lib.BuildVersionKey.FW_RW_VERSION)
151 fw_ro_build = task_params.get(build_lib.BuildVersionKey.FW_RO_VERSION)
152 if fw_ro_build:
153 build = params.software_dependencies.add()
154 build.ro_firmware_build = fw_ro_build
155 if fw_rw_build:
156 build = params.software_dependencies.add()
157 build.rw_firmware_build = fw_rw_build
158
159 test_plan = request_pb2.Request.TestPlan()
160 suite = test_plan.suite.add()
161 suite.name = task_params['suite']
162
163 return request_pb2.Request(params=params,
164 test_plan=test_plan)
165
166 def run(self, **task_params):
167 """Call TestPlatform Builder to schedule a test.
168
169 Args:
170 **task_params: The suite parameters to run.
171
172 Raises:
173 BuildbucketRunError: if failed to get build info from task parameters.
174 """
175
176 final_build = self._get_final_build(**task_params)
177 if final_build == 'None':
178 raise BuildbucketRunError('No proper build in task params: %r' %
179 task_params)
180 req = self._form_test_platform_request(final_build, **task_params)
181 req_struct = self._request_pb2_to_struct_pb2(req)
182 cred = None
183 if (self.running_env == constants.RunningEnv.ENV_STANDALONE or
184 self.running_env == constants.RunningEnv.ENV_DEVELOPMENT_SERVER):
185 # If running locally, use the staging service account.
186 sa_key = self._gen_service_account_key(
187 file_getter.STAGING_CLIENT_SECRETS_FILE)
188 cred = prpc_client.service_account_credentials(service_account_key=sa_key)
189 self.client.ScheduleBuild(self._build_request(req_struct), credentials=cred)
190
Xinan Linc61196b2019-08-13 10:37:30 -0700191 def dummy_run(self):
192 """Perform a dummy run of prpc call to cros_test_platform-dev."""
193
194 req = request_pb2.Request()
195 req_struct = self._request_pb2_to_struct_pb2(req)
196
197 # Use the staging service account to authorize the request.
198 sa_key = self._gen_service_account_key(
199 file_getter.STAGING_CLIENT_SECRETS_FILE)
200 cred = prpc_client.service_account_credentials(service_account_key=sa_key)
201 return self.client.ScheduleBuild(
202 self._build_request(req_struct), credentials=cred)
203
204 # TODO(linxinan): Handle exceptions when fail to transform proto.
205 def _request_pb2_to_struct_pb2(self, req):
Xinan Lin3ba18a02019-08-13 15:44:55 -0700206 """Transform test_platform_request to google struct proto.
Xinan Linc61196b2019-08-13 10:37:30 -0700207
Xinan Lin3ba18a02019-08-13 15:44:55 -0700208 Args:
209 req: A request_pb2 instance.
Xinan Linc61196b2019-08-13 10:37:30 -0700210
Xinan Lin3ba18a02019-08-13 15:44:55 -0700211 Returns:
212 A struct_pb2 instance.
213 """
Xinan Linc61196b2019-08-13 10:37:30 -0700214 json = json_format.MessageToJson(req)
215 structpb = struct_pb2.Struct()
216 return json_format.Parse(json, structpb, ignore_unknown_fields=True)
217
218 def _build_request(self, req_struct):
Xinan Lin3ba18a02019-08-13 15:44:55 -0700219 """Generate ScheduleBuildRequest for calling buildbucket.
Xinan Linc61196b2019-08-13 10:37:30 -0700220
Xinan Lin3ba18a02019-08-13 15:44:55 -0700221 Args:
222 req_struct: A struct_pb2 instance.
Xinan Linc61196b2019-08-13 10:37:30 -0700223
Xinan Lin3ba18a02019-08-13 15:44:55 -0700224 Returns:
225 A ScheduleBuildRequest instance.
226 """
Xinan Linc61196b2019-08-13 10:37:30 -0700227 recipe_struct = struct_pb2.Struct(fields={
228 'request': struct_pb2.Value(struct_value=req_struct)})
229 return rpc_pb2.ScheduleBuildRequest(builder=self.builder,
230 properties=recipe_struct)
231
232 def _gen_service_account_key(self, sa):
Xinan Lin3ba18a02019-08-13 15:44:55 -0700233 """Generate credentials to authorize the call.
Xinan Linc61196b2019-08-13 10:37:30 -0700234
Xinan Lin3ba18a02019-08-13 15:44:55 -0700235 Args:
236 sa: A string of the path to the service account json file.
Xinan Linc61196b2019-08-13 10:37:30 -0700237
Xinan Lin3ba18a02019-08-13 15:44:55 -0700238 Returns:
239 A service account key.
240 """
Xinan Linc61196b2019-08-13 10:37:30 -0700241 service_credentials = service_account.ServiceAccountCredentials
Xinan Lin3ba18a02019-08-13 15:44:55 -0700242 key = service_credentials.from_json_keyfile_name(sa, self.scope)
Xinan Linc61196b2019-08-13 10:37:30 -0700243 return auth.ServiceAccountKey(
244 client_email=key.service_account_email,
245 private_key=key._private_key_pkcs8_pem,
246 private_key_id=key._private_key_id)