blob: faa69dcfed6336ad65b15fb321db1f11d9c5056a [file] [log] [blame]
Aviv Keshetc679faf2019-11-27 17:52:50 -08001#!/usr/bin/env python2
Xinan Lin3ba18a02019-08-13 15:44:55 -07002# Copyright 2019 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Module for buildbucket unittests."""
7
Xinan Lindf0698a2020-02-05 22:38:11 -08008import json
Xinan Lin3ba18a02019-08-13 15:44:55 -07009import mock
10import os
11import unittest
12
Xinan Lin9e4917d2019-11-04 10:58:47 -080013import constants
Xinan Lin3ba18a02019-08-13 15:44:55 -070014import build_lib
15import buildbucket
16import task_executor
17
18from chromite.api.gen.test_platform import request_pb2
Xinan Lindf0698a2020-02-05 22:38:11 -080019from chromite.api.gen.test_platform.suite_scheduler import analytics_pb2
Xinan Lin9e4917d2019-11-04 10:58:47 -080020from google.appengine.api import taskqueue
Xinan Lin3ba18a02019-08-13 15:44:55 -070021from google.appengine.ext import testbed
22from google.protobuf import json_format
Xinan Lindf0698a2020-02-05 22:38:11 -080023from infra_libs.buildbucket.proto import build_pb2
Xinan Lin3ba18a02019-08-13 15:44:55 -070024
25
26ADDR = u'http://localhost:1'
Xinan Lindf0698a2020-02-05 22:38:11 -080027FAKE_UUID = 'c78e0bf3-4142-11ea-bc66-88e9fe4c5349'
28FAKE_BUILD_ID = 8890493019851395280
Xinan Lin3ba18a02019-08-13 15:44:55 -070029
30
Xinan Lin9e4917d2019-11-04 10:58:47 -080031def _get_suite_params(board='fake_board',
32 model='fake_model',
33 suite='fake_suite',
34 pool='DUT_POOL_SUITES',
35 cros_build='fake_cros_build'):
Xinan Lin3ba18a02019-08-13 15:44:55 -070036 return {
Prathmesh Prabhu2382a182019-09-07 21:18:10 -070037 'suite': suite,
38 'board': board,
39 'model': model,
40 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
Xinan Lin3ba18a02019-08-13 15:44:55 -070041 build_lib.BuildVersionKey.FW_RW_VERSION: 'fake_firmware_rw_build',
42 build_lib.BuildVersionKey.FW_RO_VERSION: 'fake_firmware_ro_build',
43 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: 'fake_android_build',
44 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: 'fake_testbed_build',
45 'num': 1,
Prathmesh Prabhu2382a182019-09-07 21:18:10 -070046 'pool': pool,
Xinan Lin3ba18a02019-08-13 15:44:55 -070047 'priority': 10,
Xinan Lin6e097382019-08-27 18:43:35 -070048 'timeout': 12,
49 'timeout_mins': 4320,
50 'max_runtime_mins': 4320,
Xinan Lin3ba18a02019-08-13 15:44:55 -070051 'no_wait_for_results': True,
52 'test_source_build': 'fake_test_source_build',
53 'job_retry': False,
54 'no_delay': False,
55 'force': False,
56 'run_prod_code': True,
57 'is_skylab': False,
58 }
59
60
61class FakeTestPlatformClient(object):
62
63 def __init__(self, test):
64 self._test = test
65 self.called_with_requests = []
Xinan Lindf0698a2020-02-05 22:38:11 -080066 self.error = None
Xinan Lin3ba18a02019-08-13 15:44:55 -070067
Xinan Lin57e2d962020-03-30 17:24:53 -070068 def ScheduleBuild(self, req, credentials=None, timeout=10):
Xinan Lin3ba18a02019-08-13 15:44:55 -070069 self.called_with_requests.append(req)
Xinan Lindf0698a2020-02-05 22:38:11 -080070 if self.error:
71 return self.error
72 return build_pb2.Build(id=FAKE_BUILD_ID)
73
74
75class FakeBigqueryRestClient(object):
76
77 def __init__(self, rest_client, project=None, dataset=None, table=None):
78 """Initialize the mock class."""
79 self.table = table
80 self.rows = []
81
82 def insert(self, rows):
83 self.rows = rows
84 return True
Xinan Lin3ba18a02019-08-13 15:44:55 -070085
86
87class TestPlatformClientTestCase(unittest.TestCase):
88
89 def setUp(self):
90 super(TestPlatformClientTestCase, self).setUp()
91 self.fake_client = FakeTestPlatformClient(self)
92 patcher = mock.patch('buildbucket._get_client',
93 return_value=self._get_client(ADDR))
94 patcher.start()
95 self.client = buildbucket.TestPlatformClient(ADDR,
96 'foo-proj',
97 'foo-bucket',
98 'foo-builder')
99 self.addCleanup(patcher.stop)
100 self.testbed = testbed.Testbed()
101 self.testbed.activate()
102 self.addCleanup(self.testbed.deactivate)
103 self.testbed.init_taskqueue_stub(
104 root_path=os.path.join(os.path.dirname(__file__)))
105 self.taskqueue_stub = self.testbed.get_stub(
106 testbed.TASKQUEUE_SERVICE_NAME)
Xinan Lin9e4917d2019-11-04 10:58:47 -0800107 self.queue = taskqueue.Queue(task_executor.SUITES_QUEUE)
Xinan Linc54a7462020-04-17 15:39:01 -0700108 _mock_application_id = mock.patch('constants.application_id')
109 self.mock_application_id = _mock_application_id.start()
110 self.addCleanup(_mock_application_id.stop)
111 self.mock_application_id.return_value = constants.AppID.PROD_APP
Xinan Lin3ba18a02019-08-13 15:44:55 -0700112
Xinan Lin9e4917d2019-11-04 10:58:47 -0800113 def testFormTestPlatformMultiRequestSuccessfully(self):
114 suite_kwargs = [_get_suite_params(board=b)
115 for b in ['foo', 'goo']]
116 for suite in suite_kwargs:
117 task_executor.push(task_executor.SUITES_QUEUE,
118 tag='fake_suite', **suite)
Xinan Lin3ba18a02019-08-13 15:44:55 -0700119
Xinan Lin9e4917d2019-11-04 10:58:47 -0800120 tasks = self.queue.lease_tasks_by_tag(3600,
121 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
122
123 self.client.multirequest_run(tasks, 'fake_suite')
Prathmesh Prabhu9a8060a2019-08-30 14:13:23 -0700124 self._assert_bb_client_called()
Xinan Lin9e4917d2019-11-04 10:58:47 -0800125
126 request_map = self._extract_request_map_from_bb_client()
127 request_list = [_struct_pb2_to_request_pb2(v)
128 for v in request_map.values()]
129 request_list.sort(
130 key=lambda req: req.params.software_attributes.build_target.name)
131 for i, req in enumerate(request_list):
132 self.assertEqual(req.params.scheduling.priority, 10)
133 self.assertEqual(req.params.scheduling.managed_pool,
134 request_pb2.Request.Params.Scheduling.MANAGED_POOL_SUITES)
Prathmesh Prabhu9adfa632020-02-21 10:39:24 -0800135 # Don't check for exact max timeout to stay DRY.
136 # But do check that the maximum is sane.
137 self.assertLess(req.params.time.maximum_duration.seconds, 2*24*60*60)
Aviv Keshetc679faf2019-11-27 17:52:50 -0800138 gs_url = ('gs://chromeos-image-archive/%s' %
139 suite_kwargs[i]['test_source_build'])
140 self.assertEqual(req.params.metadata.test_metadata_url, gs_url)
141 self.assertEqual(req.params.metadata.debug_symbols_archive_url, gs_url)
Xinan Lin9e4917d2019-11-04 10:58:47 -0800142 self.assertEqual(req.test_plan.suite[0].name, suite_kwargs[i]['suite'])
143 self.assertEqual(req.params.software_attributes.build_target.name,
144 suite_kwargs[i]['board'])
145
146 def testPoolName(self):
147 suite_kwargs = [_get_suite_params(board=b)
148 for b in ['foo', 'goo', 'hoo']]
149 suite_kwargs[0]['pool'] = 'cts'
150 suite_kwargs[1]['pool'] = 'wifi'
Xinan Lin9e4917d2019-11-04 10:58:47 -0800151 for suite in suite_kwargs:
152 task_executor.push(task_executor.SUITES_QUEUE,
153 tag='some_suite', **suite)
154
155 tasks = self.queue.lease_tasks_by_tag(3600,
156 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
157 self.client.multirequest_run(tasks, 'some_suite')
158 self._assert_bb_client_called()
159 request_map = self._extract_request_map_from_bb_client()
160 request_list = [_struct_pb2_to_request_pb2(v)
161 for v in request_map.values()]
162 self.assertEqual(len(request_list), 3)
163 request_list.sort(
164 key=lambda req: req.params.software_attributes.build_target.name)
165 self.assertEqual(request_list[0].params.scheduling.managed_pool,
Alex Zamorzaevc1935602019-08-28 14:37:35 -0700166 request_pb2.Request.Params.Scheduling.MANAGED_POOL_CTS)
Xinan Lin9e4917d2019-11-04 10:58:47 -0800167 self.assertEqual(request_list[1].params.scheduling.unmanaged_pool,
168 suite_kwargs[1]['pool'])
Prathmesh Prabhu88409f62019-08-30 14:32:28 -0700169
Xinan Lin3ba18a02019-08-13 15:44:55 -0700170 def testNoFinalBuild(self):
Xinan Lin9e4917d2019-11-04 10:58:47 -0800171 suite_kwargs = _get_suite_params()
172 suite_kwargs[build_lib.BuildVersionKey.CROS_VERSION] = None
173 suite_kwargs[build_lib.BuildVersionKey.ANDROID_BUILD_VERSION] = None
174 suite_kwargs[build_lib.BuildVersionKey.TESTBED_BUILD_VERSION] = None
175
176 task_executor.push(task_executor.SUITES_QUEUE,
177 tag='fake_suite', **suite_kwargs)
178
179 tasks = self.queue.lease_tasks_by_tag(3600,
180 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
181 executed = self.client.multirequest_run(tasks, 'fake_suite')
182 self.assertEqual(len(executed), 0)
Prathmesh Prabhu9a8060a2019-08-30 14:13:23 -0700183 self._assert_bb_client_not_called()
Xinan Lin3ba18a02019-08-13 15:44:55 -0700184
Xinan Lin4757d6f2020-03-24 22:20:31 -0700185 def testShouldSetQsAccount(self):
186 suite_kwargs = _get_suite_params(pool='foo')
187 suite_kwargs['priority'] = '30'
188 suite_kwargs['qs_account'] = 'qs-foo'
189
190 task_executor.push(
191 task_executor.SUITES_QUEUE, tag='fake_suite', **suite_kwargs)
192
193 tasks = self.queue.lease_tasks_by_tag(
194 3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
195 self.client.multirequest_run(tasks, 'fake_suite')
196 request_map = self._extract_request_map_from_bb_client()
197 self._assert_bb_client_called()
198 self.assertEqual(len(request_map), 1)
199 req = _struct_pb2_to_request_pb2(request_map['fake_board_fake_model'])
200 self.assertEqual(req.params.scheduling.qs_account, 'qs-foo')
201 # If qs_account is set, priority should be 0, the default value
202 # defined by proto3.
203 self.assertEqual(req.params.scheduling.priority, 0)
204
205 def testShouldNotSetQsAccountForManagedPool(self):
206 suite_kwargs = _get_suite_params(pool='DUT_POOL_SUITES')
207 suite_kwargs['qs_account'] = 'qs-foo'
208
209 task_executor.push(
210 task_executor.SUITES_QUEUE, tag='fake_suite', **suite_kwargs)
211
212 tasks = self.queue.lease_tasks_by_tag(
213 3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
214 self.client.multirequest_run(tasks, 'fake_suite')
215 request_map = self._extract_request_map_from_bb_client()
216 self._assert_bb_client_called()
217 self.assertEqual(len(request_map), 1)
218 req = _struct_pb2_to_request_pb2(request_map['fake_board_fake_model'])
219 self.assertEqual(req.params.scheduling.qs_account, '')
220
Xinan Lin9e4917d2019-11-04 10:58:47 -0800221 def testShouldSetPriority(self):
222 suite_kwargs = _get_suite_params()
223 suite_kwargs['priority'] = '30'
Xinan Linfb63d572019-09-24 15:49:04 -0700224
Xinan Lin4757d6f2020-03-24 22:20:31 -0700225 task_executor.push(
226 task_executor.SUITES_QUEUE, tag='fake_suite', **suite_kwargs)
Xinan Lin9e4917d2019-11-04 10:58:47 -0800227
228 tasks = self.queue.lease_tasks_by_tag(3600,
229 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
230 self.client.multirequest_run(tasks, 'fake_suite')
231 request_map = self._extract_request_map_from_bb_client()
Xinan Linfb63d572019-09-24 15:49:04 -0700232 self._assert_bb_client_called()
Xinan Lin9e4917d2019-11-04 10:58:47 -0800233 self.assertEqual(len(request_map), 1)
234 req = _struct_pb2_to_request_pb2(request_map['fake_board_fake_model'])
235 self.assertEqual(req.params.scheduling.priority, 30)
236
Xinan Linf1df4fc2020-04-22 21:31:48 -0700237 def testIgnorePriorityInPoolSuites(self):
238 suite_kwargs = _get_suite_params()
239 suite_kwargs['priority'] = '100'
240 suite_kwargs['pool'] = 'suites'
241
242 task_executor.push(
243 task_executor.SUITES_QUEUE, tag='fake_suite', **suite_kwargs)
244
245 tasks = self.queue.lease_tasks_by_tag(
246 3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
247 self.client.multirequest_run(tasks, 'fake_suite')
248 request_map = self._extract_request_map_from_bb_client()
249 self._assert_bb_client_called()
250 self.assertEqual(len(request_map), 1)
251 req = _struct_pb2_to_request_pb2(request_map['fake_board_fake_model'])
252 # In pool "suites", the priority should be empty or zero, the default value
253 # defined by proto3.
254 self.assertEqual(req.params.scheduling.priority, 0)
255
Xinan Lin9e4917d2019-11-04 10:58:47 -0800256 def testRequestWithInvalidTags(self):
257 suite_kwargs = [_get_suite_params(board=b)
258 for b in ['foo', 'foo']]
259
260 # test request having None String Tag
261 suite_kwargs[0]['model'] = 'None'
262
263 # test request having None Tag
264 suite_kwargs[1]['model'] = None
265
266 for suite in suite_kwargs:
267 task_executor.push(task_executor.SUITES_QUEUE,
268 tag='fake_suite', **suite)
269
270 tasks = self.queue.lease_tasks_by_tag(3600,
271 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
272 executed = self.client.multirequest_run(tasks, 'fake_suite')
273 self.assertEqual(len(executed), 2)
274
275 self._assert_bb_client_called()
276 request_map = self._extract_request_map_from_bb_client()
277 self.assertEqual(len(request_map), 2)
Xinan Linfb63d572019-09-24 15:49:04 -0700278 want = {
Xinan Lin9e4917d2019-11-04 10:58:47 -0800279 'suite:fake_suite',
280 'build:fake_cros_build',
Xinan Linfb63d572019-09-24 15:49:04 -0700281 'label-pool:DUT_POOL_SUITES',
Xinan Lin9e4917d2019-11-04 10:58:47 -0800282 'label-board:foo',
Xinan Linfb63d572019-09-24 15:49:04 -0700283 }
Xinan Lin9e4917d2019-11-04 10:58:47 -0800284 want_bb = {
285 'suite:fake_suite',
286 }
287 for req_name in ['foo', 'foo_1']:
288 req = _struct_pb2_to_request_pb2(request_map[req_name])
289 req_tags = set([t for t in req.params.decorations.tags])
290 self.assertEqual(want, req_tags)
291 bb_tags = set([t for t in self._extract_tags_from_bb_client()])
292 self.assertEqual(want_bb, bb_tags)
Xinan Linfb63d572019-09-24 15:49:04 -0700293
Prathmesh Prabhu2382a182019-09-07 21:18:10 -0700294 def testRequestTags(self):
Xinan Lin9e4917d2019-11-04 10:58:47 -0800295 suite_kwargs = _get_suite_params(
296 board='fake_board',
297 model='fake_model',
298 pool='DUT_POOL_SUITES',
299 suite='fake_suite',
300 cros_build='fake_build',
Prathmesh Prabhu2382a182019-09-07 21:18:10 -0700301 )
Xinan Linc8647112020-02-04 16:45:56 -0800302 suite_kwargs['dimensions'] = 'label-wifi:foo,label-bt:hoo'
Xinan Lin9e4917d2019-11-04 10:58:47 -0800303 task_executor.push(task_executor.SUITES_QUEUE,
304 tag='fake_suite', **suite_kwargs)
305
306 tasks = self.queue.lease_tasks_by_tag(3600,
307 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
308 self.client.multirequest_run(tasks, 'fake_suite')
Prathmesh Prabhu2382a182019-09-07 21:18:10 -0700309 self._assert_bb_client_called()
310 want = {
Xinan Lin9e4917d2019-11-04 10:58:47 -0800311 'label-board:fake_board',
312 'label-model:fake_model',
Prathmesh Prabhu2382a182019-09-07 21:18:10 -0700313 'label-pool:DUT_POOL_SUITES',
Xinan Lin9e4917d2019-11-04 10:58:47 -0800314 'suite:fake_suite',
315 'build:fake_build',
Xinan Linc8647112020-02-04 16:45:56 -0800316 'label-wifi:foo',
317 'label-bt:hoo'
Prathmesh Prabhu2382a182019-09-07 21:18:10 -0700318 }
Xinan Lin9e4917d2019-11-04 10:58:47 -0800319 want_bb = {
320 'suite:fake_suite',
321 }
322 request_map = self._extract_request_map_from_bb_client()
323 request_list = [_struct_pb2_to_request_pb2(v)
324 for v in request_map.values()]
325 self.assertEqual(len(request_list), 1)
326 req = request_list[0]
Prathmesh Prabhu2382a182019-09-07 21:18:10 -0700327 req_tags = set([t for t in req.params.decorations.tags])
328 self.assertEqual(want, req_tags)
329 bb_tags = set([t for t in self._extract_tags_from_bb_client()])
Xinan Lin9e4917d2019-11-04 10:58:47 -0800330 self.assertEqual(want_bb, bb_tags)
Xinan Lin3ba18a02019-08-13 15:44:55 -0700331
Prathmesh Prabhu73801e12019-08-30 14:09:31 -0700332 def _get_client(self, addr):
333 self.assertEqual(ADDR, addr)
334 return self.fake_client
335
Prathmesh Prabhu9a8060a2019-08-30 14:13:23 -0700336 def _assert_bb_client_called(self):
337 self.assertEqual(len(self.fake_client.called_with_requests), 1)
338
339 def _assert_bb_client_not_called(self):
340 self.assertEqual(len(self.fake_client.called_with_requests), 0)
341
Xinan Lin9e4917d2019-11-04 10:58:47 -0800342 def _extract_request_map_from_bb_client(self):
343 return self.fake_client.called_with_requests[0].properties['requests']
Prathmesh Prabhu6348ea82019-08-30 14:15:21 -0700344
Prathmesh Prabhu2382a182019-09-07 21:18:10 -0700345 def _extract_tags_from_bb_client(self):
346 return ['%s:%s' % (t.key, t.value)
347 for t in self.fake_client.called_with_requests[0].tags]
348
Prathmesh Prabhu9a8060a2019-08-30 14:13:23 -0700349
Xinan Lindf0698a2020-02-05 22:38:11 -0800350class TestTaskExecutions(TestPlatformClientTestCase):
351
352 def setUp(self):
353 super(TestTaskExecutions, self).setUp()
354 _mock_bq_client = mock.patch('rest_client.BigqueryRestClient')
355 mock_bq_client = _mock_bq_client.start()
356 self.addCleanup(_mock_bq_client.stop)
357 self.mock_bq_client = FakeBigqueryRestClient(
358 None, project='proj', dataset='dataset', table='foo')
359 mock_bq_client.return_value = self.mock_bq_client
360
361 def testRecordSuccessfulTaskExecution(self):
Xinan Lin083ba8f2020-02-06 13:55:18 -0800362 suite = _get_suite_params(board='foo')
363 suite['task_id'] = FAKE_UUID
364 task_executor.push(task_executor.SUITES_QUEUE,
365 tag='fake_suite', **suite)
Xinan Lindf0698a2020-02-05 22:38:11 -0800366 tasks = self.queue.lease_tasks_by_tag(3600,
367 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
368 self.client.multirequest_run(tasks, 'fake_suite')
369 self._assert_bb_client_called()
370 task_execution = json_format.Parse(
371 json.dumps(self.mock_bq_client.rows[0]['json']),
372 analytics_pb2.ExecutionTask())
373 self.assertEqual(task_execution.queued_task_id, FAKE_UUID)
374 self.assertEqual(task_execution.response.ctp_build_id, str(FAKE_BUILD_ID))
375 self.assertEqual(task_execution.error.error_message, '')
376
377 def testRecordFailedTaskExecution(self):
Xinan Lin083ba8f2020-02-06 13:55:18 -0800378 suite = _get_suite_params(board='foo')
379 suite['task_id'] = FAKE_UUID
380 task_executor.push(task_executor.SUITES_QUEUE,
381 tag='fake_suite', **suite)
Xinan Lindf0698a2020-02-05 22:38:11 -0800382 self.fake_client.error = "cros_test_platform error"
383 tasks = self.queue.lease_tasks_by_tag(3600,
384 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
385 self.client.multirequest_run(tasks, 'fake_suite')
386 self._assert_bb_client_called()
387 task_execution = json_format.Parse(
388 json.dumps(self.mock_bq_client.rows[0]['json']),
389 analytics_pb2.ExecutionTask())
390 self.assertEqual(task_execution.queued_task_id, FAKE_UUID)
391 self.assertEqual(task_execution.response.ctp_build_id, '')
392 self.assertEqual(task_execution.error.error_message, self.fake_client.error)
393
Xinan Lin083ba8f2020-02-06 13:55:18 -0800394 def testIgnoreTaskWithoutTaskID(self):
395 suite = _get_suite_params(board='foo')
396 task_executor.push(task_executor.SUITES_QUEUE,
397 tag='fake_suite', **suite)
398 self.fake_client.error = "cros_test_platform error"
399 tasks = self.queue.lease_tasks_by_tag(3600,
400 constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
401 self.client.multirequest_run(tasks, 'fake_suite')
402 self._assert_bb_client_called()
403 self.assertEqual(len(self.mock_bq_client.rows), 0)
404
Xinan Linc54a7462020-04-17 15:39:01 -0700405 def testIgnoreSuiteForUnamanagedPoolInStaging(self):
406 self.mock_application_id.return_value = 'suite-scheduler-staging'
407 suite = _get_suite_params(pool='foo')
408 task_executor.push(task_executor.SUITES_QUEUE, tag='fake_suite', **suite)
409 self.fake_client.error = "cros_test_platform error"
410 tasks = self.queue.lease_tasks_by_tag(
411 3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
412 self.client.multirequest_run(tasks, 'fake_suite')
413 self._assert_bb_client_not_called()
414
Xinan Lindf0698a2020-02-05 22:38:11 -0800415
Xinan Lin3ba18a02019-08-13 15:44:55 -0700416def _struct_pb2_to_request_pb2(struct_pb2):
417 """Transform google struct proto to test_platform_request.
418
419 Args:
420 struct_pb2: A struct_pb2 instance.
421
422 Returns:
423 A request_pb2 instance.
424 """
425 json = json_format.MessageToJson(struct_pb2)
426 return json_format.Parse(json, request_pb2.Request())