blob: 7616c39756d0d8db18a9aff050ca4782bef892b2 [file] [log] [blame]
Aviv Keshet6120a152017-03-22 22:37:55 -07001#!/usr/bin/env python2
Richard Barnettefddab4a2017-02-02 13:23:30 -08002# pylint: disable=missing-docstring
showardb6d16622009-05-26 19:35:29 +00003
Jiaxi Luoaac54572014-06-04 13:57:02 -07004import datetime
Allen Licdd00f22017-02-01 18:01:52 -08005import mox
Justin Giorgi67ad67d2016-06-29 14:41:04 -07006import unittest
beepse19d3032013-05-30 09:22:07 -07007
Justin Giorgi67ad67d2016-06-29 14:41:04 -07008import common
Aviv Keshet3dd8beb2013-05-13 17:36:04 -07009from autotest_lib.client.common_lib import control_data
Alex Miller4a193692013-08-21 13:59:01 -070010from autotest_lib.client.common_lib import error
Allen Licdd00f22017-02-01 18:01:52 -080011from autotest_lib.client.common_lib import global_config
Shuqian Zhao54a5b672016-05-11 22:12:17 +000012from autotest_lib.client.common_lib import priorities
Allen Licdd00f22017-02-01 18:01:52 -080013from autotest_lib.client.common_lib.cros import dev_server
Jiaxi Luoaac54572014-06-04 13:57:02 -070014from autotest_lib.client.common_lib.test_utils import mock
Allen Licdd00f22017-02-01 18:01:52 -080015from autotest_lib.frontend import setup_django_environment
16from autotest_lib.frontend.afe import frontend_test_utils
Allen Licdd00f22017-02-01 18:01:52 -080017from autotest_lib.frontend.afe import model_logic
18from autotest_lib.frontend.afe import models
19from autotest_lib.frontend.afe import rpc_interface
20from autotest_lib.frontend.afe import rpc_utils
Jakob Juelich50e91f72014-10-01 12:43:23 -070021from autotest_lib.server import frontend
Fang Deng0cb2a3b2015-12-10 17:59:00 -080022from autotest_lib.server import utils as server_utils
Allen Licdd00f22017-02-01 18:01:52 -080023from autotest_lib.server.cros import provision
24from autotest_lib.server.cros.dynamic_suite import constants
25from autotest_lib.server.cros.dynamic_suite import control_file_getter
MK Ryu9651ca52015-06-08 17:48:22 -070026from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Jacob Kopczynskid6a5e912018-03-01 09:52:34 -080027from django.db.utils import DatabaseError
28
showardb6d16622009-05-26 19:35:29 +000029
Aviv Keshet3dd8beb2013-05-13 17:36:04 -070030CLIENT = control_data.CONTROL_TYPE_NAMES.CLIENT
31SERVER = control_data.CONTROL_TYPE_NAMES.SERVER
showardb6d16622009-05-26 19:35:29 +000032
33_hqe_status = models.HostQueueEntry.Status
34
35
Xixuan Wu4379e932018-01-17 10:29:50 -080036class ShardHeartbeatTest(mox.MoxTestBase, unittest.TestCase):
37
38 _PRIORITY = priorities.Priority.DEFAULT
39
40 def _do_heartbeat_and_assert_response(self, shard_hostname='shard1',
41 upload_jobs=(), upload_hqes=(),
42 known_jobs=(), known_hosts=(),
43 **kwargs):
44 known_job_ids = [job.id for job in known_jobs]
45 known_host_ids = [host.id for host in known_hosts]
46 known_host_statuses = [host.status for host in known_hosts]
47
48 retval = rpc_interface.shard_heartbeat(
49 shard_hostname=shard_hostname,
50 jobs=upload_jobs, hqes=upload_hqes,
51 known_job_ids=known_job_ids, known_host_ids=known_host_ids,
52 known_host_statuses=known_host_statuses)
53
54 self._assert_shard_heartbeat_response(shard_hostname, retval,
55 **kwargs)
56
57 return shard_hostname
58
59
60 def _assert_shard_heartbeat_response(self, shard_hostname, retval, jobs=[],
61 hosts=[], hqes=[],
62 incorrect_host_ids=[]):
63
64 retval_hosts, retval_jobs = retval['hosts'], retval['jobs']
65 retval_incorrect_hosts = retval['incorrect_host_ids']
66
67 expected_jobs = [
68 (job.id, job.name, shard_hostname) for job in jobs]
69 returned_jobs = [(job['id'], job['name'], job['shard']['hostname'])
70 for job in retval_jobs]
71 self.assertEqual(returned_jobs, expected_jobs)
72
73 expected_hosts = [(host.id, host.hostname) for host in hosts]
74 returned_hosts = [(host['id'], host['hostname'])
75 for host in retval_hosts]
76 self.assertEqual(returned_hosts, expected_hosts)
77
78 retval_hqes = []
79 for job in retval_jobs:
80 retval_hqes += job['hostqueueentry_set']
81
82 expected_hqes = [(hqe.id) for hqe in hqes]
83 returned_hqes = [(hqe['id']) for hqe in retval_hqes]
84 self.assertEqual(returned_hqes, expected_hqes)
85
86 self.assertEqual(retval_incorrect_hosts, incorrect_host_ids)
87
88
89 def _createJobForLabel(self, label):
90 job_id = rpc_interface.create_job(name='dummy', priority=self._PRIORITY,
91 control_file='foo',
92 control_type=CLIENT,
93 meta_hosts=[label.name],
94 dependencies=(label.name,))
95 return models.Job.objects.get(id=job_id)
96
97
98 def _testShardHeartbeatFetchHostlessJobHelper(self, host1):
99 """Create a hostless job and ensure it's not assigned to a shard."""
100 label2 = models.Label.objects.create(name='bluetooth', platform=False)
101
102 job1 = self._create_job(hostless=True)
103
104 # Hostless jobs should be executed by the global scheduler.
105 self._do_heartbeat_and_assert_response(hosts=[host1])
106
107
108 def _testShardHeartbeatIncorrectHostsHelper(self, host1):
109 """Ensure that hosts that don't belong to shard are determined."""
110 host2 = models.Host.objects.create(hostname='test_host2', leased=False)
111
112 # host2 should not belong to shard1. Ensure that if shard1 thinks host2
113 # is a known host, then it is returned as invalid.
114 self._do_heartbeat_and_assert_response(known_hosts=[host1, host2],
115 incorrect_host_ids=[host2.id])
116
117
Jacob Kopczynskid6a5e912018-03-01 09:52:34 -0800118 def _testShardHeartbeatBadReadonlyQueryHelper(self, shard1, host1, label1):
119 """Ensure recovery if query fails while reading from readonly."""
120 host2 = models.Host.objects.create(hostname='test_host2', leased=False)
121 host2.labels.add(label1)
122 self.assertEqual(host2.shard, None)
123
124 proper_master_db = models.Job.objects._db
125 # In the middle of the assign_to_shard call, remove label1 from shard1.
126 self.mox.StubOutWithMock(models.Host, '_assign_to_shard_nothing_helper')
127 def find_shard_job_query():
128 return models.Job.SQL_SHARD_JOBS
129
130 def break_shard_job_query():
131 set_shard_job_query("SELECT quux from foo%s malformed query;")
132
133 def set_shard_job_query(query):
134 models.Job.SQL_SHARD_JOBS = query
135
136 models.Host._assign_to_shard_nothing_helper().WithSideEffects(
137 break_shard_job_query)
138 self.mox.ReplayAll()
139
140 old_query = find_shard_job_query()
141 try:
142 self.assertRaises(DatabaseError,
143 self._do_heartbeat_and_assert_response,
144 known_hosts=[host1], hosts=[], incorrect_host_ids=[host1.id])
145 self.assertEqual(models.Job.objects._db, proper_master_db)
146 finally:
147 set_shard_job_query(old_query)
148
149
Xixuan Wu4379e932018-01-17 10:29:50 -0800150 def _testShardHeartbeatLabelRemovalRaceHelper(self, shard1, host1, label1):
151 """Ensure correctness if label removed during heartbeat."""
152 host2 = models.Host.objects.create(hostname='test_host2', leased=False)
153 host2.labels.add(label1)
154 self.assertEqual(host2.shard, None)
155
156 # In the middle of the assign_to_shard call, remove label1 from shard1.
157 self.mox.StubOutWithMock(models.Host, '_assign_to_shard_nothing_helper')
158 def remove_label():
159 rpc_interface.remove_board_from_shard(shard1.hostname, label1.name)
160
161 models.Host._assign_to_shard_nothing_helper().WithSideEffects(
162 remove_label)
163 self.mox.ReplayAll()
164
165 self._do_heartbeat_and_assert_response(
166 known_hosts=[host1], hosts=[], incorrect_host_ids=[host1.id])
167 host2 = models.Host.smart_get(host2.id)
168 self.assertEqual(host2.shard, None)
169
170
171 def _testShardRetrieveJobsHelper(self, shard1, host1, label1, shard2,
172 host2, label2):
173 """Create jobs and retrieve them."""
174 # should never be returned by heartbeat
175 leased_host = models.Host.objects.create(hostname='leased_host',
176 leased=True)
177
178 leased_host.labels.add(label1)
179
180 job1 = self._createJobForLabel(label1)
181
182 job2 = self._createJobForLabel(label2)
183
184 job_completed = self._createJobForLabel(label1)
185 # Job is already being run, so don't sync it
186 job_completed.hostqueueentry_set.update(complete=True)
187 job_completed.hostqueueentry_set.create(complete=False)
188
189 job_active = self._createJobForLabel(label1)
190 # Job is already started, so don't sync it
191 job_active.hostqueueentry_set.update(active=True)
192 job_active.hostqueueentry_set.create(complete=False, active=False)
193
194 self._do_heartbeat_and_assert_response(
195 jobs=[job1], hosts=[host1], hqes=job1.hostqueueentry_set.all())
196
197 self._do_heartbeat_and_assert_response(
198 shard_hostname=shard2.hostname,
199 jobs=[job2], hosts=[host2], hqes=job2.hostqueueentry_set.all())
200
201 host3 = models.Host.objects.create(hostname='test_host3', leased=False)
202 host3.labels.add(label1)
203
204 self._do_heartbeat_and_assert_response(
205 known_jobs=[job1], known_hosts=[host1], hosts=[host3])
206
207
208 def _testResendJobsAfterFailedHeartbeatHelper(self, shard1, host1, label1):
209 """Create jobs, retrieve them, fail on client, fetch them again."""
210 job1 = self._createJobForLabel(label1)
211
212 self._do_heartbeat_and_assert_response(
213 jobs=[job1],
214 hqes=job1.hostqueueentry_set.all(), hosts=[host1])
215
216 # Make sure it's resubmitted by sending last_job=None again
217 self._do_heartbeat_and_assert_response(
218 known_hosts=[host1],
219 jobs=[job1], hqes=job1.hostqueueentry_set.all(), hosts=[])
220
221 # Now it worked, make sure it's not sent again
222 self._do_heartbeat_and_assert_response(
223 known_jobs=[job1], known_hosts=[host1])
224
225 job1 = models.Job.objects.get(pk=job1.id)
226 job1.hostqueueentry_set.all().update(complete=True)
227
228 # Job is completed, make sure it's not sent again
229 self._do_heartbeat_and_assert_response(
230 known_hosts=[host1])
231
232 job2 = self._createJobForLabel(label1)
233
234 # job2's creation was later, it should be returned now.
235 self._do_heartbeat_and_assert_response(
236 known_hosts=[host1],
237 jobs=[job2], hqes=job2.hostqueueentry_set.all())
238
239 self._do_heartbeat_and_assert_response(
240 known_jobs=[job2], known_hosts=[host1])
241
242 job2 = models.Job.objects.get(pk=job2.pk)
243 job2.hostqueueentry_set.update(aborted=True)
244 # Setting a job to a complete status will set the shard_id to None in
245 # scheduler_models. We have to emulate that here, because we use Django
246 # models in tests.
247 job2.shard = None
248 job2.save()
249
250 self._do_heartbeat_and_assert_response(
251 known_jobs=[job2], known_hosts=[host1],
252 jobs=[job2],
253 hqes=job2.hostqueueentry_set.all())
254
255 models.Test.objects.create(name='platform_BootPerfServer:shard',
256 test_type=1)
257 self.mox.StubOutWithMock(server_utils, 'read_file')
258 self.mox.ReplayAll()
259 rpc_interface.delete_shard(hostname=shard1.hostname)
260
261 self.assertRaises(
262 models.Shard.DoesNotExist, models.Shard.objects.get, pk=shard1.id)
263
264 job1 = models.Job.objects.get(pk=job1.id)
265 label1 = models.Label.objects.get(pk=label1.id)
266
267 self.assertIsNone(job1.shard)
268 self.assertEqual(len(label1.shard_set.all()), 0)
269
270
271 def _testResendHostsAfterFailedHeartbeatHelper(self, host1):
272 """Check that master accepts resending updated records after failure."""
273 # Send the host
274 self._do_heartbeat_and_assert_response(hosts=[host1])
275
276 # Send it again because previous one didn't persist correctly
277 self._do_heartbeat_and_assert_response(hosts=[host1])
278
279 # Now it worked, make sure it isn't sent again
280 self._do_heartbeat_and_assert_response(known_hosts=[host1])
281
282
Xixuan Wubd9d5e42018-02-20 14:19:41 -0800283class RpcInterfaceTestWithStaticAttribute(
284 mox.MoxTestBase, unittest.TestCase,
285 frontend_test_utils.FrontendTestMixin):
286
287 def setUp(self):
288 super(RpcInterfaceTestWithStaticAttribute, self).setUp()
289 self._frontend_common_setup()
290 self.god = mock.mock_god()
291 self.old_respect_static_config = rpc_interface.RESPECT_STATIC_ATTRIBUTES
292 rpc_interface.RESPECT_STATIC_ATTRIBUTES = True
293 models.RESPECT_STATIC_ATTRIBUTES = True
294
295
296 def tearDown(self):
297 self.god.unstub_all()
298 self._frontend_common_teardown()
299 global_config.global_config.reset_config_values()
300 rpc_interface.RESPECT_STATIC_ATTRIBUTES = self.old_respect_static_config
301 models.RESPECT_STATIC_ATTRIBUTES = self.old_respect_static_config
302
303
Xixuan Wubd9d5e42018-02-20 14:19:41 -0800304 def _fake_host_with_static_attributes(self):
305 host1 = models.Host.objects.create(hostname='test_host')
306 host1.set_attribute('test_attribute1', 'test_value1')
307 host1.set_attribute('test_attribute2', 'test_value2')
308 self._set_static_attribute(host1, 'test_attribute1', 'static_value1')
309 self._set_static_attribute(host1, 'static_attribute1', 'static_value2')
310 host1.save()
311 return host1
312
313
314 def test_get_hosts(self):
315 host1 = self._fake_host_with_static_attributes()
316 hosts = rpc_interface.get_hosts(hostname=host1.hostname)
317 host = hosts[0]
318
319 self.assertEquals(host['hostname'], 'test_host')
320 self.assertEquals(host['acls'], ['Everyone'])
321 # Respect the value of static attributes.
322 self.assertEquals(host['attributes'],
323 {'test_attribute1': 'static_value1',
324 'test_attribute2': 'test_value2',
325 'static_attribute1': 'static_value2'})
326
Xixuan Wu2975f402018-02-21 16:48:58 -0800327 def test_get_host_attribute_with_static(self):
328 host1 = models.Host.objects.create(hostname='test_host1')
329 host1.set_attribute('test_attribute1', 'test_value1')
330 self._set_static_attribute(host1, 'test_attribute1', 'static_value1')
331 host2 = models.Host.objects.create(hostname='test_host2')
332 host2.set_attribute('test_attribute1', 'test_value1')
333 host2.set_attribute('test_attribute2', 'test_value2')
334
335 attributes = rpc_interface.get_host_attribute(
336 'test_attribute1',
337 hostname__in=['test_host1', 'test_host2'])
338 hosts = [attr['host'] for attr in attributes]
339 values = [attr['value'] for attr in attributes]
340 self.assertEquals(set(hosts),
341 set(['test_host1', 'test_host2']))
342 self.assertEquals(set(values),
343 set(['test_value1', 'static_value1']))
344
345
Xixuan Wu91ecef52018-02-21 15:32:57 -0800346 def test_get_hosts_by_attribute_without_static(self):
347 host1 = models.Host.objects.create(hostname='test_host1')
348 host1.set_attribute('test_attribute1', 'test_value1')
349 host2 = models.Host.objects.create(hostname='test_host2')
350 host2.set_attribute('test_attribute1', 'test_value1')
351
352 hosts = rpc_interface.get_hosts_by_attribute(
353 'test_attribute1', 'test_value1')
354 self.assertEquals(set(hosts),
355 set(['test_host1', 'test_host2']))
356
357
358 def test_get_hosts_by_attribute_with_static(self):
359 host1 = models.Host.objects.create(hostname='test_host1')
360 host1.set_attribute('test_attribute1', 'test_value1')
361 self._set_static_attribute(host1, 'test_attribute1', 'test_value1')
362 host2 = models.Host.objects.create(hostname='test_host2')
363 host2.set_attribute('test_attribute1', 'test_value1')
364 self._set_static_attribute(host2, 'test_attribute1', 'static_value1')
365 host3 = models.Host.objects.create(hostname='test_host3')
366 self._set_static_attribute(host3, 'test_attribute1', 'test_value1')
367 host4 = models.Host.objects.create(hostname='test_host4')
368 host4.set_attribute('test_attribute1', 'test_value1')
369 host5 = models.Host.objects.create(hostname='test_host5')
370 host5.set_attribute('test_attribute1', 'temp_value1')
371 self._set_static_attribute(host5, 'test_attribute1', 'test_value1')
372
373 hosts = rpc_interface.get_hosts_by_attribute(
374 'test_attribute1', 'test_value1')
375 # host1: matched, it has the same value for test_attribute1.
376 # host2: not matched, it has a new value in
377 # afe_static_host_attributes for test_attribute1.
378 # host3: matched, it has a corresponding entry in
379 # afe_host_attributes for test_attribute1.
380 # host4: matched, test_attribute1 is not replaced by static
381 # attribute.
382 # host5: matched, it has an updated & matched value for
383 # test_attribute1 in afe_static_host_attributes.
384 self.assertEquals(set(hosts),
385 set(['test_host1', 'test_host3',
386 'test_host4', 'test_host5']))
387
Xixuan Wubd9d5e42018-02-20 14:19:41 -0800388
Xixuan Wu4379e932018-01-17 10:29:50 -0800389class RpcInterfaceTestWithStaticLabel(ShardHeartbeatTest,
390 frontend_test_utils.FrontendTestMixin):
391
392 _STATIC_LABELS = ['board:lumpy']
393
Xixuan Wu76cd0582017-12-21 13:23:28 -0800394 def setUp(self):
Xixuan Wu4379e932018-01-17 10:29:50 -0800395 super(RpcInterfaceTestWithStaticLabel, self).setUp()
Xixuan Wu76cd0582017-12-21 13:23:28 -0800396 self._frontend_common_setup()
397 self.god = mock.mock_god()
398 self.old_respect_static_config = rpc_interface.RESPECT_STATIC_LABELS
399 rpc_interface.RESPECT_STATIC_LABELS = True
Xixuan Wue7706032017-12-21 15:57:41 -0800400 models.RESPECT_STATIC_LABELS = True
Xixuan Wu76cd0582017-12-21 13:23:28 -0800401
402
403 def tearDown(self):
404 self.god.unstub_all()
405 self._frontend_common_teardown()
406 global_config.global_config.reset_config_values()
407 rpc_interface.RESPECT_STATIC_LABELS = self.old_respect_static_config
Xixuan Wue7706032017-12-21 15:57:41 -0800408 models.RESPECT_STATIC_LABELS = self.old_respect_static_config
Xixuan Wu76cd0582017-12-21 13:23:28 -0800409
410
Xixuan Wubd9d5e42018-02-20 14:19:41 -0800411 def _fake_host_with_static_labels(self):
412 host1 = models.Host.objects.create(hostname='test_host')
413 label1 = models.Label.objects.create(
414 name='non_static_label1', platform=False)
415 non_static_platform = models.Label.objects.create(
416 name='static_platform', platform=False)
417 static_platform = models.StaticLabel.objects.create(
418 name='static_platform', platform=True)
419 models.ReplacedLabel.objects.create(label_id=non_static_platform.id)
420 host1.static_labels.add(static_platform)
421 host1.labels.add(non_static_platform)
422 host1.labels.add(label1)
423 host1.save()
424 return host1
425
426
427 def test_get_hosts(self):
428 host1 = self._fake_host_with_static_labels()
429 hosts = rpc_interface.get_hosts(hostname=host1.hostname)
430 host = hosts[0]
431
432 self.assertEquals(host['hostname'], 'test_host')
433 self.assertEquals(host['acls'], ['Everyone'])
434 # Respect all labels in afe_hosts_labels.
435 self.assertEquals(host['labels'],
436 ['non_static_label1', 'static_platform'])
437 # Respect static labels.
438 self.assertEquals(host['platform'], 'static_platform')
439
440
441 def test_get_hosts_multiple_labels(self):
442 self._fake_host_with_static_labels()
443 hosts = rpc_interface.get_hosts(
444 multiple_labels=['non_static_label1', 'static_platform'])
445 host = hosts[0]
446 self.assertEquals(host['hostname'], 'test_host')
447
448
Xixuan Wu76cd0582017-12-21 13:23:28 -0800449 def test_delete_static_label(self):
450 label1 = models.Label.smart_get('static')
451
452 host2 = models.Host.objects.all()[1]
453 shard1 = models.Shard.objects.create(hostname='shard1')
454 host2.shard = shard1
455 host2.labels.add(label1)
456 host2.save()
457
458 mock_afe = self.god.create_mock_class_obj(frontend_wrappers.RetryingAFE,
459 'MockAFE')
460 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
461
462 self.assertRaises(error.UnmodifiableLabelException,
463 rpc_interface.delete_label,
464 label1.id)
465
466 self.god.check_playback()
467
468
Xixuan Wuc820ea82017-12-21 13:28:53 -0800469 def test_modify_static_label(self):
470 label1 = models.Label.smart_get('static')
471 self.assertEqual(label1.invalid, 0)
472
473 host2 = models.Host.objects.all()[1]
474 shard1 = models.Shard.objects.create(hostname='shard1')
475 host2.shard = shard1
476 host2.labels.add(label1)
477 host2.save()
478
479 mock_afe = self.god.create_mock_class_obj(frontend_wrappers.RetryingAFE,
480 'MockAFE')
481 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
482
483 self.assertRaises(error.UnmodifiableLabelException,
484 rpc_interface.modify_label,
485 label1.id,
486 invalid=1)
487
488 self.assertEqual(models.Label.smart_get('static').invalid, 0)
489 self.god.check_playback()
490
491
Xixuan Wue7706032017-12-21 15:57:41 -0800492 def test_multiple_platforms_add_non_static_to_static(self):
493 """Test non-static platform to a host with static platform."""
494 static_platform = models.StaticLabel.objects.create(
495 name='static_platform', platform=True)
Xixuan Wu79cbf492018-01-11 16:36:57 -0800496 non_static_platform = models.Label.objects.create(
497 name='static_platform', platform=True)
498 models.ReplacedLabel.objects.create(label_id=non_static_platform.id)
Xixuan Wue7706032017-12-21 15:57:41 -0800499 platform2 = models.Label.objects.create(name='platform2', platform=True)
500 host1 = models.Host.objects.create(hostname='test_host')
501 host1.static_labels.add(static_platform)
Xixuan Wu79cbf492018-01-11 16:36:57 -0800502 host1.labels.add(non_static_platform)
Xixuan Wue7706032017-12-21 15:57:41 -0800503 host1.save()
504
505 self.assertRaises(model_logic.ValidationError,
506 rpc_interface.label_add_hosts, id='platform2',
507 hosts=['test_host'])
508 self.assertRaises(model_logic.ValidationError,
509 rpc_interface.host_add_labels,
510 id='test_host', labels=['platform2'])
511 # make sure the platform didn't get added
512 platforms = rpc_interface.get_labels(
513 host__hostname__in=['test_host'], platform=True)
Xixuan Wu79cbf492018-01-11 16:36:57 -0800514 self.assertEquals(len(platforms), 1)
Xixuan Wue7706032017-12-21 15:57:41 -0800515
516
517 def test_multiple_platforms_add_static_to_non_static(self):
518 """Test static platform to a host with non-static platform."""
519 platform1 = models.Label.objects.create(
520 name='static_platform', platform=True)
521 models.ReplacedLabel.objects.create(label_id=platform1.id)
522 static_platform = models.StaticLabel.objects.create(
523 name='static_platform', platform=True)
524 platform2 = models.Label.objects.create(
525 name='platform2', platform=True)
526
527 host1 = models.Host.objects.create(hostname='test_host')
528 host1.labels.add(platform2)
529 host1.save()
530
531 self.assertRaises(model_logic.ValidationError,
532 rpc_interface.label_add_hosts,
533 id='static_platform',
534 hosts=['test_host'])
535 self.assertRaises(model_logic.ValidationError,
536 rpc_interface.host_add_labels,
537 id='test_host', labels=['static_platform'])
538 # make sure the platform didn't get added
539 platforms = rpc_interface.get_labels(
540 host__hostname__in=['test_host'], platform=True)
541 self.assertEquals(len(platforms), 1)
542
543
Xixuan Wu040fc8b2018-01-12 12:52:14 -0800544 def test_label_remove_hosts(self):
545 """Test remove a label of hosts."""
546 label = models.Label.smart_get('static')
547 static_label = models.StaticLabel.objects.create(name='static')
548
549 host1 = models.Host.objects.create(hostname='test_host')
550 host1.labels.add(label)
551 host1.static_labels.add(static_label)
552 host1.save()
553
554 self.assertRaises(error.UnmodifiableLabelException,
555 rpc_interface.label_remove_hosts,
556 id='static', hosts=['test_host'])
557
558
559 def test_host_remove_labels(self):
560 """Test remove labels of a given host."""
561 label = models.Label.smart_get('static')
562 label1 = models.Label.smart_get('label1')
563 label2 = models.Label.smart_get('label2')
564 static_label = models.StaticLabel.objects.create(name='static')
565
566 host1 = models.Host.objects.create(hostname='test_host')
567 host1.labels.add(label)
568 host1.labels.add(label1)
569 host1.labels.add(label2)
570 host1.static_labels.add(static_label)
571 host1.save()
572
573 rpc_interface.host_remove_labels(
574 'test_host', ['static', 'label1'])
575 labels = rpc_interface.get_labels(host__hostname__in=['test_host'])
576 # Only non_static label 'label1' is removed.
577 self.assertEquals(len(labels), 2)
578 self.assertEquals(labels[0].get('name'), 'label2')
579
580
Xixuan Wu20b323f2018-01-12 13:58:43 -0800581 def test_remove_board_from_shard(self):
582 """test remove a board (static label) from shard."""
583 label = models.Label.smart_get('static')
584 static_label = models.StaticLabel.objects.create(name='static')
585
586 shard = models.Shard.objects.create(hostname='test_shard')
587 shard.labels.add(label)
588
589 host = models.Host.objects.create(hostname='test_host',
590 leased=False,
591 shard=shard)
592 host.static_labels.add(static_label)
593 host.save()
594
595 rpc_interface.remove_board_from_shard(shard.hostname, label.name)
596 host1 = models.Host.smart_get(host.id)
597 shard1 = models.Shard.smart_get(shard.id)
598 self.assertEqual(host1.shard, None)
599 self.assertItemsEqual(shard1.labels.all(), [])
600
601
Xixuan Wu2dc10ce2018-01-16 14:51:58 -0800602 def test_check_job_dependencies_success(self):
603 """Test check_job_dependencies successfully."""
604 static_label = models.StaticLabel.objects.create(name='static')
605
606 host = models.Host.objects.create(hostname='test_host')
607 host.static_labels.add(static_label)
608 host.save()
609
610 host1 = models.Host.smart_get(host.id)
611 rpc_utils.check_job_dependencies([host1], ['static'])
612
613
614 def test_check_job_dependencies_fail(self):
615 """Test check_job_dependencies with raising ValidationError."""
616 label = models.Label.smart_get('static')
617 static_label = models.StaticLabel.objects.create(name='static')
618
619 host = models.Host.objects.create(hostname='test_host')
620 host.labels.add(label)
621 host.save()
622
623 host1 = models.Host.smart_get(host.id)
624 self.assertRaises(model_logic.ValidationError,
625 rpc_utils.check_job_dependencies,
626 [host1],
627 ['static'])
628
629 def test_check_job_metahost_dependencies_success(self):
630 """Test check_job_metahost_dependencies successfully."""
631 label1 = models.Label.smart_get('label1')
632 label2 = models.Label.smart_get('label2')
633 label = models.Label.smart_get('static')
634 static_label = models.StaticLabel.objects.create(name='static')
635
636 host = models.Host.objects.create(hostname='test_host')
637 host.static_labels.add(static_label)
638 host.labels.add(label1)
639 host.labels.add(label2)
640 host.save()
641
642 rpc_utils.check_job_metahost_dependencies(
643 [label1, label], [label2.name])
644 rpc_utils.check_job_metahost_dependencies(
645 [label1], [label2.name, static_label.name])
646
647
648 def test_check_job_metahost_dependencies_fail(self):
649 """Test check_job_metahost_dependencies with raising errors."""
650 label1 = models.Label.smart_get('label1')
651 label2 = models.Label.smart_get('label2')
652 label = models.Label.smart_get('static')
653 static_label = models.StaticLabel.objects.create(name='static')
654
655 host = models.Host.objects.create(hostname='test_host')
656 host.labels.add(label1)
657 host.labels.add(label2)
658 host.save()
659
660 self.assertRaises(error.NoEligibleHostException,
661 rpc_utils.check_job_metahost_dependencies,
662 [label1, label], [label2.name])
663 self.assertRaises(error.NoEligibleHostException,
664 rpc_utils.check_job_metahost_dependencies,
665 [label1], [label2.name, static_label.name])
666
667
Xixuan Wu4379e932018-01-17 10:29:50 -0800668 def _createShardAndHostWithStaticLabel(self,
669 shard_hostname='shard1',
670 host_hostname='test_host1',
671 label_name='board:lumpy'):
672 label = models.Label.objects.create(name=label_name)
673
674 shard = models.Shard.objects.create(hostname=shard_hostname)
675 shard.labels.add(label)
676
677 host = models.Host.objects.create(hostname=host_hostname, leased=False,
678 shard=shard)
679 host.labels.add(label)
680 if label_name in self._STATIC_LABELS:
681 models.ReplacedLabel.objects.create(label_id=label.id)
682 static_label = models.StaticLabel.objects.create(name=label_name)
683 host.static_labels.add(static_label)
684
685 return shard, host, label
686
687
688 def testShardHeartbeatFetchHostlessJob(self):
689 shard1, host1, label1 = self._createShardAndHostWithStaticLabel(
690 host_hostname='test_host1')
691 self._testShardHeartbeatFetchHostlessJobHelper(host1)
692
693
694 def testShardHeartbeatIncorrectHosts(self):
695 shard1, host1, label1 = self._createShardAndHostWithStaticLabel(
696 host_hostname='test_host1')
697 self._testShardHeartbeatIncorrectHostsHelper(host1)
698
699
700 def testShardHeartbeatLabelRemovalRace(self):
701 shard1, host1, label1 = self._createShardAndHostWithStaticLabel(
702 host_hostname='test_host1')
703 self._testShardHeartbeatLabelRemovalRaceHelper(shard1, host1, label1)
704
705
706 def testShardRetrieveJobs(self):
707 shard1, host1, label1 = self._createShardAndHostWithStaticLabel()
708 shard2, host2, label2 = self._createShardAndHostWithStaticLabel(
709 'shard2', 'test_host2', 'board:grumpy')
710 self._testShardRetrieveJobsHelper(shard1, host1, label1,
711 shard2, host2, label2)
712
713
714 def testResendJobsAfterFailedHeartbeat(self):
715 shard1, host1, label1 = self._createShardAndHostWithStaticLabel()
716 self._testResendJobsAfterFailedHeartbeatHelper(shard1, host1, label1)
717
718
719 def testResendHostsAfterFailedHeartbeat(self):
720 shard1, host1, label1 = self._createShardAndHostWithStaticLabel(
721 host_hostname='test_host1')
722 self._testResendHostsAfterFailedHeartbeatHelper(host1)
723
724
showardb6d16622009-05-26 19:35:29 +0000725class RpcInterfaceTest(unittest.TestCase,
726 frontend_test_utils.FrontendTestMixin):
727 def setUp(self):
728 self._frontend_common_setup()
Jiaxi Luoaac54572014-06-04 13:57:02 -0700729 self.god = mock.mock_god()
showardb6d16622009-05-26 19:35:29 +0000730
731
732 def tearDown(self):
Jiaxi Luoaac54572014-06-04 13:57:02 -0700733 self.god.unstub_all()
showardb6d16622009-05-26 19:35:29 +0000734 self._frontend_common_teardown()
Jakob Juelich50e91f72014-10-01 12:43:23 -0700735 global_config.global_config.reset_config_values()
showardb6d16622009-05-26 19:35:29 +0000736
737
showarda5288b42009-07-28 20:06:08 +0000738 def test_validation(self):
showarda5288b42009-07-28 20:06:08 +0000739 # omit a required field
740 self.assertRaises(model_logic.ValidationError, rpc_interface.add_label,
741 name=None)
742 # violate uniqueness constraint
743 self.assertRaises(model_logic.ValidationError, rpc_interface.add_host,
744 hostname='host1')
745
746
showardcafd16e2009-05-29 18:37:49 +0000747 def test_multiple_platforms(self):
748 platform2 = models.Label.objects.create(name='platform2', platform=True)
749 self.assertRaises(model_logic.ValidationError,
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800750 rpc_interface. label_add_hosts, id='platform2',
751 hosts=['host1', 'host2'])
showardcafd16e2009-05-29 18:37:49 +0000752 self.assertRaises(model_logic.ValidationError,
MK Ryufbb002c2015-06-08 14:13:16 -0700753 rpc_interface.host_add_labels,
754 id='host1', labels=['platform2'])
showardcafd16e2009-05-29 18:37:49 +0000755 # make sure the platform didn't get added
756 platforms = rpc_interface.get_labels(
757 host__hostname__in=['host1', 'host2'], platform=True)
758 self.assertEquals(len(platforms), 1)
759 self.assertEquals(platforms[0]['name'], 'myplatform')
760
761
showarda5288b42009-07-28 20:06:08 +0000762 def _check_hostnames(self, hosts, expected_hostnames):
763 self.assertEquals(set(host['hostname'] for host in hosts),
764 set(expected_hostnames))
765
766
Chris Ching5e7d4a72017-06-15 10:28:59 -0600767 def test_ping_db(self):
768 self.assertEquals(rpc_interface.ping_db(), [True])
769
770
Xixuan Wu91ecef52018-02-21 15:32:57 -0800771 def test_get_hosts_by_attribute(self):
772 host1 = models.Host.objects.create(hostname='test_host1')
773 host1.set_attribute('test_attribute1', 'test_value1')
774 host2 = models.Host.objects.create(hostname='test_host2')
775 host2.set_attribute('test_attribute1', 'test_value1')
776
777 hosts = rpc_interface.get_hosts_by_attribute(
778 'test_attribute1', 'test_value1')
779 self.assertEquals(set(hosts),
780 set(['test_host1', 'test_host2']))
781
782
Xixuan Wu2975f402018-02-21 16:48:58 -0800783 def test_get_host_attribute(self):
784 host1 = models.Host.objects.create(hostname='test_host1')
785 host1.set_attribute('test_attribute1', 'test_value1')
786 host2 = models.Host.objects.create(hostname='test_host2')
787 host2.set_attribute('test_attribute1', 'test_value1')
788
789 attributes = rpc_interface.get_host_attribute(
790 'test_attribute1',
791 hostname__in=['test_host1', 'test_host2'])
792 hosts = [attr['host'] for attr in attributes]
793 values = [attr['value'] for attr in attributes]
794 self.assertEquals(set(hosts),
795 set(['test_host1', 'test_host2']))
796 self.assertEquals(set(values), set(['test_value1']))
797
798
showarda5288b42009-07-28 20:06:08 +0000799 def test_get_hosts(self):
800 hosts = rpc_interface.get_hosts()
801 self._check_hostnames(hosts, [host.hostname for host in self.hosts])
802
803 hosts = rpc_interface.get_hosts(hostname='host1')
804 self._check_hostnames(hosts, ['host1'])
showard7e67b432010-01-20 01:13:04 +0000805 host = hosts[0]
806 self.assertEquals(sorted(host['labels']), ['label1', 'myplatform'])
807 self.assertEquals(host['platform'], 'myplatform')
showard7e67b432010-01-20 01:13:04 +0000808 self.assertEquals(host['acls'], ['my_acl'])
809 self.assertEquals(host['attributes'], {})
showarda5288b42009-07-28 20:06:08 +0000810
811
812 def test_get_hosts_multiple_labels(self):
813 hosts = rpc_interface.get_hosts(
814 multiple_labels=['myplatform', 'label1'])
815 self._check_hostnames(hosts, ['host1'])
816
817
showardc1a98d12010-01-15 00:22:22 +0000818 def test_job_keyvals(self):
819 keyval_dict = {'mykey': 'myvalue'}
Allen Li352b86a2016-12-14 12:11:27 -0800820 job_id = rpc_interface.create_job(name='test',
821 priority=priorities.Priority.DEFAULT,
showardc1a98d12010-01-15 00:22:22 +0000822 control_file='foo',
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700823 control_type=CLIENT,
showardc1a98d12010-01-15 00:22:22 +0000824 hosts=['host1'],
825 keyvals=keyval_dict)
826 jobs = rpc_interface.get_jobs(id=job_id)
827 self.assertEquals(len(jobs), 1)
828 self.assertEquals(jobs[0]['keyvals'], keyval_dict)
829
830
Aviv Keshetcd1ff9b2013-03-01 14:55:19 -0800831 def test_test_retry(self):
Allen Li352b86a2016-12-14 12:11:27 -0800832 job_id = rpc_interface.create_job(name='flake',
833 priority=priorities.Priority.DEFAULT,
Aviv Keshetcd1ff9b2013-03-01 14:55:19 -0800834 control_file='foo',
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700835 control_type=CLIENT,
Aviv Keshetcd1ff9b2013-03-01 14:55:19 -0800836 hosts=['host1'],
837 test_retry=10)
838 jobs = rpc_interface.get_jobs(id=job_id)
839 self.assertEquals(len(jobs), 1)
840 self.assertEquals(jobs[0]['test_retry'], 10)
841
842
showardb6d16622009-05-26 19:35:29 +0000843 def test_get_jobs_summary(self):
showardc0ac3a72009-07-08 21:14:45 +0000844 job = self._create_job(hosts=xrange(1, 4))
showardb6d16622009-05-26 19:35:29 +0000845 entries = list(job.hostqueueentry_set.all())
846 entries[1].status = _hqe_status.FAILED
847 entries[1].save()
848 entries[2].status = _hqe_status.FAILED
849 entries[2].aborted = True
850 entries[2].save()
851
Jiaxi Luoaac54572014-06-04 13:57:02 -0700852 # Mock up tko_rpc_interface.get_status_counts.
853 self.god.stub_function_to_return(rpc_interface.tko_rpc_interface,
854 'get_status_counts',
855 None)
856
showardb6d16622009-05-26 19:35:29 +0000857 job_summaries = rpc_interface.get_jobs_summary(id=job.id)
858 self.assertEquals(len(job_summaries), 1)
859 summary = job_summaries[0]
860 self.assertEquals(summary['status_counts'], {'Queued': 1,
861 'Failed': 2})
862
863
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700864 def _check_job_ids(self, actual_job_dicts, expected_jobs):
865 self.assertEquals(
866 set(job_dict['id'] for job_dict in actual_job_dicts),
867 set(job.id for job in expected_jobs))
868
869
870 def test_get_jobs_status_filters(self):
showard6c65d252009-10-01 18:45:22 +0000871 HqeStatus = models.HostQueueEntry.Status
872 def create_two_host_job():
873 return self._create_job(hosts=[1, 2])
874 def set_hqe_statuses(job, first_status, second_status):
875 entries = job.hostqueueentry_set.all()
876 entries[0].update_object(status=first_status)
877 entries[1].update_object(status=second_status)
878
879 queued = create_two_host_job()
880
881 queued_and_running = create_two_host_job()
882 set_hqe_statuses(queued_and_running, HqeStatus.QUEUED,
883 HqeStatus.RUNNING)
884
885 running_and_complete = create_two_host_job()
886 set_hqe_statuses(running_and_complete, HqeStatus.RUNNING,
887 HqeStatus.COMPLETED)
888
889 complete = create_two_host_job()
890 set_hqe_statuses(complete, HqeStatus.COMPLETED, HqeStatus.COMPLETED)
891
892 started_but_inactive = create_two_host_job()
893 set_hqe_statuses(started_but_inactive, HqeStatus.QUEUED,
894 HqeStatus.COMPLETED)
895
896 parsing = create_two_host_job()
897 set_hqe_statuses(parsing, HqeStatus.PARSING, HqeStatus.PARSING)
898
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700899 self._check_job_ids(rpc_interface.get_jobs(not_yet_run=True), [queued])
900 self._check_job_ids(rpc_interface.get_jobs(running=True),
showard6c65d252009-10-01 18:45:22 +0000901 [queued_and_running, running_and_complete,
902 started_but_inactive, parsing])
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700903 self._check_job_ids(rpc_interface.get_jobs(finished=True), [complete])
904
905
906 def test_get_jobs_type_filters(self):
907 self.assertRaises(AssertionError, rpc_interface.get_jobs,
908 suite=True, sub=True)
909 self.assertRaises(AssertionError, rpc_interface.get_jobs,
910 suite=True, standalone=True)
911 self.assertRaises(AssertionError, rpc_interface.get_jobs,
912 standalone=True, sub=True)
913
914 parent_job = self._create_job(hosts=[1])
915 child_jobs = self._create_job(hosts=[1, 2],
916 parent_job_id=parent_job.id)
917 standalone_job = self._create_job(hosts=[1])
918
919 self._check_job_ids(rpc_interface.get_jobs(suite=True), [parent_job])
920 self._check_job_ids(rpc_interface.get_jobs(sub=True), [child_jobs])
921 self._check_job_ids(rpc_interface.get_jobs(standalone=True),
922 [standalone_job])
showard6c65d252009-10-01 18:45:22 +0000923
924
showarda5288b42009-07-28 20:06:08 +0000925 def _create_job_helper(self, **kwargs):
Allen Li352b86a2016-12-14 12:11:27 -0800926 return rpc_interface.create_job(name='test',
927 priority=priorities.Priority.DEFAULT,
MK Ryue301eb72015-06-25 12:51:02 -0700928 control_file='control file',
929 control_type=SERVER, **kwargs)
showarda5288b42009-07-28 20:06:08 +0000930
931
showard2924b0a2009-06-18 23:16:15 +0000932 def test_one_time_hosts(self):
showarda5288b42009-07-28 20:06:08 +0000933 job = self._create_job_helper(one_time_hosts=['testhost'])
showard2924b0a2009-06-18 23:16:15 +0000934 host = models.Host.objects.get(hostname='testhost')
935 self.assertEquals(host.invalid, True)
936 self.assertEquals(host.labels.count(), 0)
937 self.assertEquals(host.aclgroup_set.count(), 0)
938
939
showard09d80f92009-11-19 01:01:19 +0000940 def test_create_job_duplicate_hosts(self):
941 self.assertRaises(model_logic.ValidationError, self._create_job_helper,
942 hosts=[1, 1])
943
944
Alex Miller4a193692013-08-21 13:59:01 -0700945 def test_create_unrunnable_metahost_job(self):
946 self.assertRaises(error.NoEligibleHostException,
947 self._create_job_helper, meta_hosts=['unused'])
948
949
showarda9545c02009-12-18 22:44:26 +0000950 def test_create_hostless_job(self):
951 job_id = self._create_job_helper(hostless=True)
952 job = models.Job.objects.get(pk=job_id)
953 queue_entries = job.hostqueueentry_set.all()
954 self.assertEquals(len(queue_entries), 1)
955 self.assertEquals(queue_entries[0].host, None)
956 self.assertEquals(queue_entries[0].meta_host, None)
showarda9545c02009-12-18 22:44:26 +0000957
958
showard1a5a4082009-07-28 20:01:37 +0000959 def _setup_special_tasks(self):
showardc0ac3a72009-07-08 21:14:45 +0000960 host = self.hosts[0]
961
962 job1 = self._create_job(hosts=[1])
963 job2 = self._create_job(hosts=[1])
964
965 entry1 = job1.hostqueueentry_set.all()[0]
966 entry1.update_object(started_on=datetime.datetime(2009, 1, 2),
showardd1195652009-12-08 22:21:02 +0000967 execution_subdir='host1')
showardc0ac3a72009-07-08 21:14:45 +0000968 entry2 = job2.hostqueueentry_set.all()[0]
969 entry2.update_object(started_on=datetime.datetime(2009, 1, 3),
showardd1195652009-12-08 22:21:02 +0000970 execution_subdir='host1')
showardc0ac3a72009-07-08 21:14:45 +0000971
showard1a5a4082009-07-28 20:01:37 +0000972 self.task1 = models.SpecialTask.objects.create(
showardc0ac3a72009-07-08 21:14:45 +0000973 host=host, task=models.SpecialTask.Task.VERIFY,
974 time_started=datetime.datetime(2009, 1, 1), # ran before job 1
jamesren76fcf192010-04-21 20:39:50 +0000975 is_complete=True, requested_by=models.User.current_user())
showard1a5a4082009-07-28 20:01:37 +0000976 self.task2 = models.SpecialTask.objects.create(
showardc0ac3a72009-07-08 21:14:45 +0000977 host=host, task=models.SpecialTask.Task.VERIFY,
978 queue_entry=entry2, # ran with job 2
jamesren76fcf192010-04-21 20:39:50 +0000979 is_active=True, requested_by=models.User.current_user())
showard1a5a4082009-07-28 20:01:37 +0000980 self.task3 = models.SpecialTask.objects.create(
jamesren76fcf192010-04-21 20:39:50 +0000981 host=host, task=models.SpecialTask.Task.VERIFY,
982 requested_by=models.User.current_user()) # not yet run
showardc0ac3a72009-07-08 21:14:45 +0000983
showard1a5a4082009-07-28 20:01:37 +0000984
985 def test_get_special_tasks(self):
986 self._setup_special_tasks()
987 tasks = rpc_interface.get_special_tasks(host__hostname='host1',
988 queue_entry__isnull=True)
989 self.assertEquals(len(tasks), 2)
990 self.assertEquals(tasks[0]['task'], models.SpecialTask.Task.VERIFY)
991 self.assertEquals(tasks[0]['is_active'], False)
992 self.assertEquals(tasks[0]['is_complete'], True)
993
994
995 def test_get_latest_special_task(self):
996 # a particular usage of get_special_tasks()
997 self._setup_special_tasks()
998 self.task2.time_started = datetime.datetime(2009, 1, 2)
999 self.task2.save()
1000
1001 tasks = rpc_interface.get_special_tasks(
1002 host__hostname='host1', task=models.SpecialTask.Task.VERIFY,
1003 time_started__isnull=False, sort_by=['-time_started'],
1004 query_limit=1)
1005 self.assertEquals(len(tasks), 1)
1006 self.assertEquals(tasks[0]['id'], 2)
1007
1008
1009 def _common_entry_check(self, entry_dict):
1010 self.assertEquals(entry_dict['host']['hostname'], 'host1')
1011 self.assertEquals(entry_dict['job']['id'], 2)
1012
1013
1014 def test_get_host_queue_entries_and_special_tasks(self):
1015 self._setup_special_tasks()
1016
MK Ryu0c1a37d2015-04-30 12:00:55 -07001017 host = self.hosts[0].id
showardc0ac3a72009-07-08 21:14:45 +00001018 entries_and_tasks = (
Jiaxi Luo79ce6422014-06-13 17:08:09 -07001019 rpc_interface.get_host_queue_entries_and_special_tasks(host))
showardc0ac3a72009-07-08 21:14:45 +00001020
1021 paths = [entry['execution_path'] for entry in entries_and_tasks]
1022 self.assertEquals(paths, ['hosts/host1/3-verify',
showardfd8b89f2010-01-20 19:06:30 +00001023 '2-autotest_system/host1',
showardc0ac3a72009-07-08 21:14:45 +00001024 'hosts/host1/2-verify',
showardfd8b89f2010-01-20 19:06:30 +00001025 '1-autotest_system/host1',
showardc0ac3a72009-07-08 21:14:45 +00001026 'hosts/host1/1-verify'])
1027
1028 verify2 = entries_and_tasks[2]
1029 self._common_entry_check(verify2)
1030 self.assertEquals(verify2['type'], 'Verify')
1031 self.assertEquals(verify2['status'], 'Running')
1032 self.assertEquals(verify2['execution_path'], 'hosts/host1/2-verify')
1033
1034 entry2 = entries_and_tasks[1]
1035 self._common_entry_check(entry2)
1036 self.assertEquals(entry2['type'], 'Job')
1037 self.assertEquals(entry2['status'], 'Queued')
1038 self.assertEquals(entry2['started_on'], '2009-01-03 00:00:00')
1039
1040
Paul Hobbs341165b2017-05-25 16:53:12 -07001041 def _create_hqes_and_start_time_index_entries(self):
1042 shard = models.Shard.objects.create(hostname='shard')
1043 job = self._create_job(shard=shard, control_file='foo')
1044 HqeStatus = models.HostQueueEntry.Status
1045
Paul Hobbs74a56112017-11-03 19:33:37 +00001046 models.HostQueueEntry(
1047 id=1, job=job, started_on='2017-01-01',
1048 status=HqeStatus.QUEUED).save()
1049 models.HostQueueEntry(
1050 id=2, job=job, started_on='2017-01-02',
1051 status=HqeStatus.QUEUED).save()
1052 models.HostQueueEntry(
1053 id=3, job=job, started_on='2017-01-03',
1054 status=HqeStatus.QUEUED).save()
Paul Hobbs341165b2017-05-25 16:53:12 -07001055
Paul Hobbs74a56112017-11-03 19:33:37 +00001056 models.HostQueueEntryStartTimes(
1057 insert_time='2017-01-03', highest_hqe_id=3).save()
1058 models.HostQueueEntryStartTimes(
1059 insert_time='2017-01-02', highest_hqe_id=2).save()
1060 models.HostQueueEntryStartTimes(
1061 insert_time='2017-01-01', highest_hqe_id=1).save()
1062
1063 def test_get_host_queue_entries_by_insert_time(self):
1064 """Check the insert_time_after and insert_time_before constraints."""
Paul Hobbs341165b2017-05-25 16:53:12 -07001065 self._create_hqes_and_start_time_index_entries()
1066 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
Paul Hobbs74a56112017-11-03 19:33:37 +00001067 insert_time_after='2017-01-01')
Paul Hobbs341165b2017-05-25 16:53:12 -07001068 self.assertEquals(len(hqes), 3)
1069
1070 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
Paul Hobbs74a56112017-11-03 19:33:37 +00001071 insert_time_after='2017-01-02')
1072 self.assertEquals(len(hqes), 2)
1073
1074 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
1075 insert_time_after='2017-01-03')
1076 self.assertEquals(len(hqes), 1)
1077
1078 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
1079 insert_time_before='2017-01-01')
1080 self.assertEquals(len(hqes), 1)
1081
1082 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
1083 insert_time_before='2017-01-02')
1084 self.assertEquals(len(hqes), 2)
1085
1086 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
1087 insert_time_before='2017-01-03')
1088 self.assertEquals(len(hqes), 3)
1089
1090
1091 def test_get_host_queue_entries_by_insert_time_with_missing_index_row(self):
1092 """Shows that the constraints are approximate.
1093
1094 The query may return rows which are actually outside of the bounds
1095 given, if the index table does not have an entry for the specific time.
1096 """
1097 self._create_hqes_and_start_time_index_entries()
1098 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
1099 insert_time_before='2016-12-01')
1100 self.assertEquals(len(hqes), 1)
Paul Hobbs341165b2017-05-25 16:53:12 -07001101
1102 def test_get_hqe_by_insert_time_with_before_and_after(self):
1103 self._create_hqes_and_start_time_index_entries()
1104 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
Paul Hobbs74a56112017-11-03 19:33:37 +00001105 insert_time_before='2017-01-02',
1106 insert_time_after='2017-01-02')
1107 self.assertEquals(len(hqes), 1)
Paul Hobbs341165b2017-05-25 16:53:12 -07001108
1109 def test_get_hqe_by_insert_time_and_id_constraint(self):
1110 self._create_hqes_and_start_time_index_entries()
Paul Hobbs74a56112017-11-03 19:33:37 +00001111 # The time constraint is looser than the id constraint, so the time
Paul Hobbs341165b2017-05-25 16:53:12 -07001112 # constraint should take precedence.
1113 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
Paul Hobbs74a56112017-11-03 19:33:37 +00001114 insert_time_before='2017-01-02',
Paul Hobbs341165b2017-05-25 16:53:12 -07001115 id__lte=1)
Paul Hobbs74a56112017-11-03 19:33:37 +00001116 self.assertEquals(len(hqes), 1)
Paul Hobbs341165b2017-05-25 16:53:12 -07001117
1118 # Now make the time constraint tighter than the id constraint.
1119 hqes = rpc_interface.get_host_queue_entries_by_insert_time(
Paul Hobbs74a56112017-11-03 19:33:37 +00001120 insert_time_before='2017-01-01',
Paul Hobbs341165b2017-05-25 16:53:12 -07001121 id__lte=42)
Paul Hobbs74a56112017-11-03 19:33:37 +00001122 self.assertEquals(len(hqes), 1)
Paul Hobbs341165b2017-05-25 16:53:12 -07001123
showard8aa84fc2009-09-16 17:17:55 +00001124 def test_view_invalid_host(self):
1125 # RPCs used by View Host page should work for invalid hosts
1126 self._create_job_helper(hosts=[1])
Jiaxi Luo79ce6422014-06-13 17:08:09 -07001127 host = self.hosts[0]
1128 host.delete()
showard8aa84fc2009-09-16 17:17:55 +00001129
1130 self.assertEquals(1, rpc_interface.get_num_hosts(hostname='host1',
1131 valid_only=False))
1132 data = rpc_interface.get_hosts(hostname='host1', valid_only=False)
1133 self.assertEquals(1, len(data))
1134
1135 self.assertEquals(1, rpc_interface.get_num_host_queue_entries(
1136 host__hostname='host1'))
1137 data = rpc_interface.get_host_queue_entries(host__hostname='host1')
1138 self.assertEquals(1, len(data))
1139
1140 count = rpc_interface.get_num_host_queue_entries_and_special_tasks(
MK Ryu0c1a37d2015-04-30 12:00:55 -07001141 host=host.id)
showard8aa84fc2009-09-16 17:17:55 +00001142 self.assertEquals(1, count)
1143 data = rpc_interface.get_host_queue_entries_and_special_tasks(
MK Ryu0c1a37d2015-04-30 12:00:55 -07001144 host=host.id)
showard8aa84fc2009-09-16 17:17:55 +00001145 self.assertEquals(1, len(data))
1146
1147
showard9bb960b2009-11-19 01:02:11 +00001148 def test_reverify_hosts(self):
mbligh4e545a52009-12-19 05:30:39 +00001149 hostname_list = rpc_interface.reverify_hosts(id__in=[1, 2])
1150 self.assertEquals(hostname_list, ['host1', 'host2'])
showard9bb960b2009-11-19 01:02:11 +00001151 tasks = rpc_interface.get_special_tasks()
1152 self.assertEquals(len(tasks), 2)
1153 self.assertEquals(set(task['host']['id'] for task in tasks),
1154 set([1, 2]))
1155
1156 task = tasks[0]
1157 self.assertEquals(task['task'], models.SpecialTask.Task.VERIFY)
showardfd8b89f2010-01-20 19:06:30 +00001158 self.assertEquals(task['requested_by'], 'autotest_system')
showard9bb960b2009-11-19 01:02:11 +00001159
1160
Simran Basi73dae552013-02-25 14:57:46 -08001161 def test_repair_hosts(self):
1162 hostname_list = rpc_interface.repair_hosts(id__in=[1, 2])
1163 self.assertEquals(hostname_list, ['host1', 'host2'])
1164 tasks = rpc_interface.get_special_tasks()
1165 self.assertEquals(len(tasks), 2)
1166 self.assertEquals(set(task['host']['id'] for task in tasks),
1167 set([1, 2]))
1168
1169 task = tasks[0]
1170 self.assertEquals(task['task'], models.SpecialTask.Task.REPAIR)
1171 self.assertEquals(task['requested_by'], 'autotest_system')
1172
1173
Jakob Juelich50e91f72014-10-01 12:43:23 -07001174 def _modify_host_helper(self, on_shard=False, host_on_shard=False):
1175 shard_hostname = 'shard1'
1176 if on_shard:
1177 global_config.global_config.override_config_value(
1178 'SHARD', 'shard_hostname', shard_hostname)
1179
1180 host = models.Host.objects.all()[0]
1181 if host_on_shard:
1182 shard = models.Shard.objects.create(hostname=shard_hostname)
1183 host.shard = shard
1184 host.save()
1185
1186 self.assertFalse(host.locked)
1187
1188 self.god.stub_class_method(frontend.AFE, 'run')
1189
1190 if host_on_shard and not on_shard:
1191 mock_afe = self.god.create_mock_class_obj(
MK Ryu9651ca52015-06-08 17:48:22 -07001192 frontend_wrappers.RetryingAFE, 'MockAFE')
1193 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
Jakob Juelich50e91f72014-10-01 12:43:23 -07001194
MK Ryu9651ca52015-06-08 17:48:22 -07001195 mock_afe2 = frontend_wrappers.RetryingAFE.expect_new(
MK Ryu0a9c82e2015-09-17 17:54:01 -07001196 server=shard_hostname, user=None)
MK Ryu33889612015-09-04 14:32:35 -07001197 mock_afe2.run.expect_call('modify_host_local', id=host.id,
MK Ryud53e1492015-12-15 12:09:03 -08001198 locked=True, lock_reason='_modify_host_helper lock',
1199 lock_time=datetime.datetime(2015, 12, 15))
MK Ryu33889612015-09-04 14:32:35 -07001200 elif on_shard:
1201 mock_afe = self.god.create_mock_class_obj(
1202 frontend_wrappers.RetryingAFE, 'MockAFE')
1203 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
1204
1205 mock_afe2 = frontend_wrappers.RetryingAFE.expect_new(
Fang Deng0cb2a3b2015-12-10 17:59:00 -08001206 server=server_utils.get_global_afe_hostname(), user=None)
MK Ryu33889612015-09-04 14:32:35 -07001207 mock_afe2.run.expect_call('modify_host', id=host.id,
MK Ryud53e1492015-12-15 12:09:03 -08001208 locked=True, lock_reason='_modify_host_helper lock',
1209 lock_time=datetime.datetime(2015, 12, 15))
Jakob Juelich50e91f72014-10-01 12:43:23 -07001210
Matthew Sartori68186332015-04-27 17:19:53 -07001211 rpc_interface.modify_host(id=host.id, locked=True,
MK Ryud53e1492015-12-15 12:09:03 -08001212 lock_reason='_modify_host_helper lock',
1213 lock_time=datetime.datetime(2015, 12, 15))
Jakob Juelich50e91f72014-10-01 12:43:23 -07001214
1215 host = models.Host.objects.get(pk=host.id)
MK Ryu33889612015-09-04 14:32:35 -07001216 if on_shard:
1217 # modify_host on shard does nothing but routing the RPC to master.
1218 self.assertFalse(host.locked)
1219 else:
1220 self.assertTrue(host.locked)
Jakob Juelich50e91f72014-10-01 12:43:23 -07001221 self.god.check_playback()
1222
1223
1224 def test_modify_host_on_master_host_on_master(self):
MK Ryu33889612015-09-04 14:32:35 -07001225 """Call modify_host to master for host in master."""
Jakob Juelich50e91f72014-10-01 12:43:23 -07001226 self._modify_host_helper()
1227
1228
1229 def test_modify_host_on_master_host_on_shard(self):
MK Ryu33889612015-09-04 14:32:35 -07001230 """Call modify_host to master for host in shard."""
Jakob Juelich50e91f72014-10-01 12:43:23 -07001231 self._modify_host_helper(host_on_shard=True)
1232
1233
1234 def test_modify_host_on_shard(self):
MK Ryu33889612015-09-04 14:32:35 -07001235 """Call modify_host to shard for host in shard."""
Jakob Juelich50e91f72014-10-01 12:43:23 -07001236 self._modify_host_helper(on_shard=True, host_on_shard=True)
1237
1238
1239 def test_modify_hosts_on_master_host_on_shard(self):
1240 """Ensure calls to modify_hosts are correctly forwarded to shards."""
1241 host1 = models.Host.objects.all()[0]
1242 host2 = models.Host.objects.all()[1]
1243
1244 shard1 = models.Shard.objects.create(hostname='shard1')
1245 host1.shard = shard1
1246 host1.save()
1247
1248 shard2 = models.Shard.objects.create(hostname='shard2')
1249 host2.shard = shard2
1250 host2.save()
1251
1252 self.assertFalse(host1.locked)
1253 self.assertFalse(host2.locked)
1254
MK Ryu9651ca52015-06-08 17:48:22 -07001255 mock_afe = self.god.create_mock_class_obj(frontend_wrappers.RetryingAFE,
MK Ryu33889612015-09-04 14:32:35 -07001256 'MockAFE')
MK Ryu9651ca52015-06-08 17:48:22 -07001257 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
Jakob Juelich50e91f72014-10-01 12:43:23 -07001258
1259 # The statuses of one host might differ on master and shard.
1260 # Filters are always applied on the master. So the host on the shard
1261 # will be affected no matter what his status is.
1262 filters_to_use = {'status': 'Ready'}
1263
MK Ryu0a9c82e2015-09-17 17:54:01 -07001264 mock_afe2 = frontend_wrappers.RetryingAFE.expect_new(
1265 server='shard2', user=None)
Jakob Juelich50e91f72014-10-01 12:43:23 -07001266 mock_afe2.run.expect_call(
MK Ryu33889612015-09-04 14:32:35 -07001267 'modify_hosts_local',
Jakob Juelich50e91f72014-10-01 12:43:23 -07001268 host_filter_data={'id__in': [shard1.id, shard2.id]},
Matthew Sartori68186332015-04-27 17:19:53 -07001269 update_data={'locked': True,
MK Ryud53e1492015-12-15 12:09:03 -08001270 'lock_reason': 'Testing forward to shard',
1271 'lock_time' : datetime.datetime(2015, 12, 15) })
Jakob Juelich50e91f72014-10-01 12:43:23 -07001272
MK Ryu0a9c82e2015-09-17 17:54:01 -07001273 mock_afe1 = frontend_wrappers.RetryingAFE.expect_new(
1274 server='shard1', user=None)
Jakob Juelich50e91f72014-10-01 12:43:23 -07001275 mock_afe1.run.expect_call(
MK Ryu33889612015-09-04 14:32:35 -07001276 'modify_hosts_local',
Jakob Juelich50e91f72014-10-01 12:43:23 -07001277 host_filter_data={'id__in': [shard1.id, shard2.id]},
Matthew Sartori68186332015-04-27 17:19:53 -07001278 update_data={'locked': True,
MK Ryud53e1492015-12-15 12:09:03 -08001279 'lock_reason': 'Testing forward to shard',
1280 'lock_time' : datetime.datetime(2015, 12, 15)})
Jakob Juelich50e91f72014-10-01 12:43:23 -07001281
MK Ryud53e1492015-12-15 12:09:03 -08001282 rpc_interface.modify_hosts(
1283 host_filter_data={'status': 'Ready'},
1284 update_data={'locked': True,
1285 'lock_reason': 'Testing forward to shard',
1286 'lock_time' : datetime.datetime(2015, 12, 15) })
Jakob Juelich50e91f72014-10-01 12:43:23 -07001287
1288 host1 = models.Host.objects.get(pk=host1.id)
1289 self.assertTrue(host1.locked)
1290 host2 = models.Host.objects.get(pk=host2.id)
1291 self.assertTrue(host2.locked)
1292 self.god.check_playback()
1293
1294
1295 def test_delete_host(self):
1296 """Ensure an RPC is made on delete a host, if it is on a shard."""
1297 host1 = models.Host.objects.all()[0]
1298 shard1 = models.Shard.objects.create(hostname='shard1')
1299 host1.shard = shard1
1300 host1.save()
1301 host1_id = host1.id
1302
MK Ryu9651ca52015-06-08 17:48:22 -07001303 mock_afe = self.god.create_mock_class_obj(frontend_wrappers.RetryingAFE,
Jakob Juelich50e91f72014-10-01 12:43:23 -07001304 'MockAFE')
MK Ryu9651ca52015-06-08 17:48:22 -07001305 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
Jakob Juelich50e91f72014-10-01 12:43:23 -07001306
MK Ryu0a9c82e2015-09-17 17:54:01 -07001307 mock_afe1 = frontend_wrappers.RetryingAFE.expect_new(
1308 server='shard1', user=None)
Jakob Juelich50e91f72014-10-01 12:43:23 -07001309 mock_afe1.run.expect_call('delete_host', id=host1.id)
1310
1311 rpc_interface.delete_host(id=host1.id)
1312
1313 self.assertRaises(models.Host.DoesNotExist,
1314 models.Host.smart_get, host1_id)
1315
1316 self.god.check_playback()
1317
1318
MK Ryu8e2c2d02016-01-06 15:24:38 -08001319 def test_modify_label(self):
1320 label1 = models.Label.objects.all()[0]
1321 self.assertEqual(label1.invalid, 0)
1322
1323 host2 = models.Host.objects.all()[1]
1324 shard1 = models.Shard.objects.create(hostname='shard1')
1325 host2.shard = shard1
1326 host2.labels.add(label1)
1327 host2.save()
1328
1329 mock_afe = self.god.create_mock_class_obj(frontend_wrappers.RetryingAFE,
1330 'MockAFE')
1331 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
1332
1333 mock_afe1 = frontend_wrappers.RetryingAFE.expect_new(
1334 server='shard1', user=None)
1335 mock_afe1.run.expect_call('modify_label', id=label1.id, invalid=1)
1336
1337 rpc_interface.modify_label(label1.id, invalid=1)
1338
1339 self.assertEqual(models.Label.objects.all()[0].invalid, 1)
1340 self.god.check_playback()
1341
1342
1343 def test_delete_label(self):
1344 label1 = models.Label.objects.all()[0]
1345
1346 host2 = models.Host.objects.all()[1]
1347 shard1 = models.Shard.objects.create(hostname='shard1')
1348 host2.shard = shard1
1349 host2.labels.add(label1)
1350 host2.save()
1351
1352 mock_afe = self.god.create_mock_class_obj(frontend_wrappers.RetryingAFE,
1353 'MockAFE')
1354 self.god.stub_with(frontend_wrappers, 'RetryingAFE', mock_afe)
1355
1356 mock_afe1 = frontend_wrappers.RetryingAFE.expect_new(
1357 server='shard1', user=None)
1358 mock_afe1.run.expect_call('delete_label', id=label1.id)
1359
1360 rpc_interface.delete_label(id=label1.id)
1361
1362 self.assertRaises(models.Label.DoesNotExist,
1363 models.Label.smart_get, label1.id)
1364 self.god.check_playback()
1365
1366
Michael Tang6dc174e2016-05-31 23:13:42 -07001367 def test_get_image_for_job_with_keyval_build(self):
1368 keyval_dict = {'build': 'cool-image'}
Allen Li352b86a2016-12-14 12:11:27 -08001369 job_id = rpc_interface.create_job(name='test',
1370 priority=priorities.Priority.DEFAULT,
Michael Tang6dc174e2016-05-31 23:13:42 -07001371 control_file='foo',
1372 control_type=CLIENT,
1373 hosts=['host1'],
1374 keyvals=keyval_dict)
1375 job = models.Job.objects.get(id=job_id)
1376 self.assertIsNotNone(job)
1377 image = rpc_interface._get_image_for_job(job, True)
1378 self.assertEquals('cool-image', image)
1379
1380
1381 def test_get_image_for_job_with_keyval_builds(self):
1382 keyval_dict = {'builds': {'cros-version': 'cool-image'}}
Allen Li352b86a2016-12-14 12:11:27 -08001383 job_id = rpc_interface.create_job(name='test',
1384 priority=priorities.Priority.DEFAULT,
Michael Tang6dc174e2016-05-31 23:13:42 -07001385 control_file='foo',
1386 control_type=CLIENT,
1387 hosts=['host1'],
1388 keyvals=keyval_dict)
1389 job = models.Job.objects.get(id=job_id)
1390 self.assertIsNotNone(job)
1391 image = rpc_interface._get_image_for_job(job, True)
1392 self.assertEquals('cool-image', image)
1393
1394
1395 def test_get_image_for_job_with_control_build(self):
1396 CONTROL_FILE = """build='cool-image'
1397 """
Allen Li352b86a2016-12-14 12:11:27 -08001398 job_id = rpc_interface.create_job(name='test',
1399 priority=priorities.Priority.DEFAULT,
Michael Tang6dc174e2016-05-31 23:13:42 -07001400 control_file='foo',
1401 control_type=CLIENT,
1402 hosts=['host1'])
1403 job = models.Job.objects.get(id=job_id)
1404 self.assertIsNotNone(job)
1405 job.control_file = CONTROL_FILE
1406 image = rpc_interface._get_image_for_job(job, True)
1407 self.assertEquals('cool-image', image)
1408
1409
1410 def test_get_image_for_job_with_control_builds(self):
1411 CONTROL_FILE = """builds={'cros-version': 'cool-image'}
1412 """
Allen Li352b86a2016-12-14 12:11:27 -08001413 job_id = rpc_interface.create_job(name='test',
1414 priority=priorities.Priority.DEFAULT,
Michael Tang6dc174e2016-05-31 23:13:42 -07001415 control_file='foo',
1416 control_type=CLIENT,
1417 hosts=['host1'])
1418 job = models.Job.objects.get(id=job_id)
1419 self.assertIsNotNone(job)
1420 job.control_file = CONTROL_FILE
1421 image = rpc_interface._get_image_for_job(job, True)
1422 self.assertEquals('cool-image', image)
1423
1424
Xixuan Wu4379e932018-01-17 10:29:50 -08001425class ExtraRpcInterfaceTest(frontend_test_utils.FrontendTestMixin,
1426 ShardHeartbeatTest):
Allen Licdd00f22017-02-01 18:01:52 -08001427 """Unit tests for functions originally in site_rpc_interface.py.
1428
1429 @var _NAME: fake suite name.
1430 @var _BOARD: fake board to reimage.
1431 @var _BUILD: fake build with which to reimage.
1432 @var _PRIORITY: fake priority with which to reimage.
1433 """
1434 _NAME = 'name'
1435 _BOARD = 'link'
1436 _BUILD = 'link-release/R36-5812.0.0'
1437 _BUILDS = {provision.CROS_VERSION_PREFIX: _BUILD}
1438 _PRIORITY = priorities.Priority.DEFAULT
1439 _TIMEOUT = 24
1440
1441
1442 def setUp(self):
1443 super(ExtraRpcInterfaceTest, self).setUp()
1444 self._SUITE_NAME = rpc_interface.canonicalize_suite_name(
1445 self._NAME)
1446 self.dev_server = self.mox.CreateMock(dev_server.ImageServer)
1447 self._frontend_common_setup(fill_data=False)
Jacob Kopczynskid6a5e912018-03-01 09:52:34 -08001448 self.stored_readonly_setting = models.Job.FETCH_READONLY_JOBS
1449 models.Job.FETCH_READONLY_JOBS = True
Allen Licdd00f22017-02-01 18:01:52 -08001450
1451
1452 def tearDown(self):
1453 self._frontend_common_teardown()
Jacob Kopczynskid6a5e912018-03-01 09:52:34 -08001454 models.Job.FETCH_READONLY_JOBS = self.stored_readonly_setting
Allen Licdd00f22017-02-01 18:01:52 -08001455
1456
1457 def _setupDevserver(self):
1458 self.mox.StubOutClassWithMocks(dev_server, 'ImageServer')
1459 dev_server.resolve(self._BUILD).AndReturn(self.dev_server)
1460
1461
1462 def _mockDevServerGetter(self, get_control_file=True):
1463 self._setupDevserver()
1464 if get_control_file:
1465 self.getter = self.mox.CreateMock(
1466 control_file_getter.DevServerGetter)
1467 self.mox.StubOutWithMock(control_file_getter.DevServerGetter,
1468 'create')
1469 control_file_getter.DevServerGetter.create(
1470 mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(self.getter)
1471
1472
1473 def _mockRpcUtils(self, to_return, control_file_substring=''):
1474 """Fake out the autotest rpc_utils module with a mockable class.
1475
1476 @param to_return: the value that rpc_utils.create_job_common() should
1477 be mocked out to return.
1478 @param control_file_substring: A substring that is expected to appear
1479 in the control file output string that
1480 is passed to create_job_common.
1481 Default: ''
1482 """
1483 download_started_time = constants.DOWNLOAD_STARTED_TIME
1484 payload_finished_time = constants.PAYLOAD_FINISHED_TIME
1485 self.mox.StubOutWithMock(rpc_utils, 'create_job_common')
1486 rpc_utils.create_job_common(mox.And(mox.StrContains(self._NAME),
1487 mox.StrContains(self._BUILD)),
1488 priority=self._PRIORITY,
1489 timeout_mins=self._TIMEOUT*60,
1490 max_runtime_mins=self._TIMEOUT*60,
1491 control_type='Server',
1492 control_file=mox.And(mox.StrContains(self._BOARD),
1493 mox.StrContains(self._BUILD),
1494 mox.StrContains(
1495 control_file_substring)),
1496 hostless=True,
1497 keyvals=mox.And(mox.In(download_started_time),
1498 mox.In(payload_finished_time))
1499 ).AndReturn(to_return)
1500
1501
1502 def testStageBuildFail(self):
1503 """Ensure that a failure to stage the desired build fails the RPC."""
1504 self._setupDevserver()
1505
1506 self.dev_server.hostname = 'mox_url'
1507 self.dev_server.stage_artifacts(
1508 image=self._BUILD, artifacts=['test_suites']).AndRaise(
1509 dev_server.DevServerException())
1510 self.mox.ReplayAll()
1511 self.assertRaises(error.StageControlFileFailure,
1512 rpc_interface.create_suite_job,
1513 name=self._NAME,
1514 board=self._BOARD,
1515 builds=self._BUILDS,
1516 pool=None)
1517
1518
1519 def testGetControlFileFail(self):
1520 """Ensure that a failure to get needed control file fails the RPC."""
1521 self._mockDevServerGetter()
1522
1523 self.dev_server.hostname = 'mox_url'
1524 self.dev_server.stage_artifacts(
1525 image=self._BUILD, artifacts=['test_suites']).AndReturn(True)
1526
1527 self.getter.get_control_file_contents_by_name(
1528 self._SUITE_NAME).AndReturn(None)
1529 self.mox.ReplayAll()
1530 self.assertRaises(error.ControlFileEmpty,
1531 rpc_interface.create_suite_job,
1532 name=self._NAME,
1533 board=self._BOARD,
1534 builds=self._BUILDS,
1535 pool=None)
1536
1537
1538 def testGetControlFileListFail(self):
1539 """Ensure that a failure to get needed control file fails the RPC."""
1540 self._mockDevServerGetter()
1541
1542 self.dev_server.hostname = 'mox_url'
1543 self.dev_server.stage_artifacts(
1544 image=self._BUILD, artifacts=['test_suites']).AndReturn(True)
1545
1546 self.getter.get_control_file_contents_by_name(
1547 self._SUITE_NAME).AndRaise(error.NoControlFileList())
1548 self.mox.ReplayAll()
1549 self.assertRaises(error.NoControlFileList,
1550 rpc_interface.create_suite_job,
1551 name=self._NAME,
1552 board=self._BOARD,
1553 builds=self._BUILDS,
1554 pool=None)
1555
1556
Allen Licdd00f22017-02-01 18:01:52 -08001557 def testCreateSuiteJobFail(self):
1558 """Ensure that failure to schedule the suite job fails the RPC."""
1559 self._mockDevServerGetter()
1560
1561 self.dev_server.hostname = 'mox_url'
1562 self.dev_server.stage_artifacts(
1563 image=self._BUILD, artifacts=['test_suites']).AndReturn(True)
1564
1565 self.getter.get_control_file_contents_by_name(
1566 self._SUITE_NAME).AndReturn('f')
1567
1568 self.dev_server.url().AndReturn('mox_url')
1569 self._mockRpcUtils(-1)
1570 self.mox.ReplayAll()
1571 self.assertEquals(
1572 rpc_interface.create_suite_job(name=self._NAME,
1573 board=self._BOARD,
1574 builds=self._BUILDS, pool=None),
1575 -1)
1576
1577
1578 def testCreateSuiteJobSuccess(self):
1579 """Ensures that success results in a successful RPC."""
1580 self._mockDevServerGetter()
1581
1582 self.dev_server.hostname = 'mox_url'
1583 self.dev_server.stage_artifacts(
1584 image=self._BUILD, artifacts=['test_suites']).AndReturn(True)
1585
1586 self.getter.get_control_file_contents_by_name(
1587 self._SUITE_NAME).AndReturn('f')
1588
1589 self.dev_server.url().AndReturn('mox_url')
1590 job_id = 5
1591 self._mockRpcUtils(job_id)
1592 self.mox.ReplayAll()
1593 self.assertEquals(
1594 rpc_interface.create_suite_job(name=self._NAME,
1595 board=self._BOARD,
1596 builds=self._BUILDS,
1597 pool=None),
1598 job_id)
1599
1600
1601 def testCreateSuiteJobNoHostCheckSuccess(self):
1602 """Ensures that success results in a successful RPC."""
1603 self._mockDevServerGetter()
1604
1605 self.dev_server.hostname = 'mox_url'
1606 self.dev_server.stage_artifacts(
1607 image=self._BUILD, artifacts=['test_suites']).AndReturn(True)
1608
1609 self.getter.get_control_file_contents_by_name(
1610 self._SUITE_NAME).AndReturn('f')
1611
1612 self.dev_server.url().AndReturn('mox_url')
1613 job_id = 5
1614 self._mockRpcUtils(job_id)
1615 self.mox.ReplayAll()
1616 self.assertEquals(
1617 rpc_interface.create_suite_job(name=self._NAME,
1618 board=self._BOARD,
1619 builds=self._BUILDS,
1620 pool=None, check_hosts=False),
1621 job_id)
1622
Allen Licdd00f22017-02-01 18:01:52 -08001623
1624 def testCreateSuiteJobControlFileSupplied(self):
1625 """Ensure we can supply the control file to create_suite_job."""
1626 self._mockDevServerGetter(get_control_file=False)
1627
1628 self.dev_server.hostname = 'mox_url'
1629 self.dev_server.stage_artifacts(
1630 image=self._BUILD, artifacts=['test_suites']).AndReturn(True)
1631 self.dev_server.url().AndReturn('mox_url')
1632 job_id = 5
1633 self._mockRpcUtils(job_id)
1634 self.mox.ReplayAll()
1635 self.assertEquals(
1636 rpc_interface.create_suite_job(name='%s/%s' % (self._NAME,
1637 self._BUILD),
1638 board=None,
1639 builds=self._BUILDS,
1640 pool=None,
1641 control_file='CONTROL FILE'),
1642 job_id)
1643
1644
1645 def _get_records_for_sending_to_master(self):
1646 return [{'control_file': 'foo',
1647 'control_type': 1,
1648 'created_on': datetime.datetime(2014, 8, 21),
1649 'drone_set': None,
1650 'email_list': '',
1651 'max_runtime_hrs': 72,
1652 'max_runtime_mins': 1440,
1653 'name': 'dummy',
1654 'owner': 'autotest_system',
1655 'parse_failed_repair': True,
1656 'priority': 40,
1657 'reboot_after': 0,
1658 'reboot_before': 1,
1659 'run_reset': True,
1660 'run_verify': False,
1661 'synch_count': 0,
1662 'test_retry': 10,
1663 'timeout': 24,
1664 'timeout_mins': 1440,
1665 'id': 1
1666 }], [{
1667 'aborted': False,
1668 'active': False,
1669 'complete': False,
1670 'deleted': False,
1671 'execution_subdir': '',
1672 'finished_on': None,
1673 'started_on': None,
1674 'status': 'Queued',
1675 'id': 1
1676 }]
1677
1678
Allen Licdd00f22017-02-01 18:01:52 -08001679 def _send_records_to_master_helper(
1680 self, jobs, hqes, shard_hostname='host1',
1681 exception_to_throw=error.UnallowedRecordsSentToMaster, aborted=False):
1682 job_id = rpc_interface.create_job(
1683 name='dummy',
1684 priority=self._PRIORITY,
1685 control_file='foo',
1686 control_type=SERVER,
1687 test_retry=10, hostless=True)
1688 job = models.Job.objects.get(pk=job_id)
1689 shard = models.Shard.objects.create(hostname='host1')
1690 job.shard = shard
1691 job.save()
1692
1693 if aborted:
1694 job.hostqueueentry_set.update(aborted=True)
1695 job.shard = None
1696 job.save()
1697
1698 hqe = job.hostqueueentry_set.all()[0]
1699 if not exception_to_throw:
1700 self._do_heartbeat_and_assert_response(
1701 shard_hostname=shard_hostname,
1702 upload_jobs=jobs, upload_hqes=hqes)
1703 else:
1704 self.assertRaises(
1705 exception_to_throw,
1706 self._do_heartbeat_and_assert_response,
1707 shard_hostname=shard_hostname,
1708 upload_jobs=jobs, upload_hqes=hqes)
1709
1710
1711 def testSendingRecordsToMaster(self):
1712 """Send records to the master and ensure they are persisted."""
1713 jobs, hqes = self._get_records_for_sending_to_master()
1714 hqes[0]['status'] = 'Completed'
1715 self._send_records_to_master_helper(
1716 jobs=jobs, hqes=hqes, exception_to_throw=None)
1717
1718 # Check the entry was actually written to db
1719 self.assertEqual(models.HostQueueEntry.objects.all()[0].status,
1720 'Completed')
1721
1722
1723 def testSendingRecordsToMasterAbortedOnMaster(self):
1724 """Send records to the master and ensure they are persisted."""
1725 jobs, hqes = self._get_records_for_sending_to_master()
1726 hqes[0]['status'] = 'Completed'
1727 self._send_records_to_master_helper(
1728 jobs=jobs, hqes=hqes, exception_to_throw=None, aborted=True)
1729
1730 # Check the entry was actually written to db
1731 self.assertEqual(models.HostQueueEntry.objects.all()[0].status,
1732 'Completed')
1733
1734
1735 def testSendingRecordsToMasterJobAssignedToDifferentShard(self):
Aviv Keshet6dec0e12017-04-17 15:23:33 -07001736 """Ensure records belonging to different shard are silently rejected."""
1737 shard1 = models.Shard.objects.create(hostname='shard1')
1738 shard2 = models.Shard.objects.create(hostname='shard2')
1739 job1 = self._create_job(shard=shard1, control_file='foo1')
1740 job2 = self._create_job(shard=shard2, control_file='foo2')
1741 job1_id = job1.id
1742 job2_id = job2.id
1743 hqe1 = models.HostQueueEntry.objects.create(job=job1)
1744 hqe2 = models.HostQueueEntry.objects.create(job=job2)
1745 hqe1_id = hqe1.id
1746 hqe2_id = hqe2.id
1747 job1_record = job1.serialize(include_dependencies=False)
1748 job2_record = job2.serialize(include_dependencies=False)
1749 hqe1_record = hqe1.serialize(include_dependencies=False)
1750 hqe2_record = hqe2.serialize(include_dependencies=False)
Allen Licdd00f22017-02-01 18:01:52 -08001751
Aviv Keshet6dec0e12017-04-17 15:23:33 -07001752 # Prepare a bogus job record update from the wrong shard. The update
1753 # should not throw an exception. Non-bogus jobs in the same update
1754 # should happily update.
1755 job1_record.update({'control_file': 'bar1'})
1756 job2_record.update({'control_file': 'bar2'})
1757 hqe1_record.update({'status': 'Aborted'})
1758 hqe2_record.update({'status': 'Aborted'})
1759 self._do_heartbeat_and_assert_response(
1760 shard_hostname='shard2', upload_jobs=[job1_record, job2_record],
1761 upload_hqes=[hqe1_record, hqe2_record])
Allen Licdd00f22017-02-01 18:01:52 -08001762
Aviv Keshet6dec0e12017-04-17 15:23:33 -07001763 # Job and HQE record for wrong job should not be modified, because the
1764 # rpc came from the wrong shard. Job and HQE record for valid job are
1765 # modified.
1766 self.assertEqual(models.Job.objects.get(id=job1_id).control_file,
1767 'foo1')
1768 self.assertEqual(models.Job.objects.get(id=job2_id).control_file,
1769 'bar2')
1770 self.assertEqual(models.HostQueueEntry.objects.get(id=hqe1_id).status,
1771 '')
1772 self.assertEqual(models.HostQueueEntry.objects.get(id=hqe2_id).status,
1773 'Aborted')
Allen Licdd00f22017-02-01 18:01:52 -08001774
1775
1776 def testSendingRecordsToMasterNotExistingJob(self):
1777 """Ensure update for non existing job gets rejected."""
1778 jobs, hqes = self._get_records_for_sending_to_master()
1779 jobs[0]['id'] = 3
1780
1781 self._send_records_to_master_helper(
1782 jobs=jobs, hqes=hqes)
1783
1784
1785 def _createShardAndHostWithLabel(self, shard_hostname='shard1',
1786 host_hostname='host1',
1787 label_name='board:lumpy'):
Aviv Keshet4be5a162017-03-23 08:59:06 -07001788 """Create a label, host, shard, and assign host to shard."""
Xixuan Wu4379e932018-01-17 10:29:50 -08001789 try:
1790 label = models.Label.objects.create(name=label_name)
1791 except:
1792 label = models.Label.smart_get(label_name)
Allen Licdd00f22017-02-01 18:01:52 -08001793
1794 shard = models.Shard.objects.create(hostname=shard_hostname)
1795 shard.labels.add(label)
1796
Aviv Keshet4be5a162017-03-23 08:59:06 -07001797 host = models.Host.objects.create(hostname=host_hostname, leased=False,
1798 shard=shard)
Allen Licdd00f22017-02-01 18:01:52 -08001799 host.labels.add(label)
1800
1801 return shard, host, label
1802
1803
Aviv Keshet224b2202017-04-04 14:26:44 -07001804 def testShardLabelRemovalInvalid(self):
1805 """Ensure you cannot remove the wrong label from shard."""
1806 shard1, host1, lumpy_label = self._createShardAndHostWithLabel()
1807 stumpy_label = models.Label.objects.create(
1808 name='board:stumpy', platform=True)
1809 with self.assertRaises(error.RPCException):
1810 rpc_interface.remove_board_from_shard(
1811 shard1.hostname, stumpy_label.name)
1812
1813
1814 def testShardHeartbeatLabelRemoval(self):
1815 """Ensure label removal from shard works."""
1816 shard1, host1, lumpy_label = self._createShardAndHostWithLabel()
1817
1818 self.assertEqual(host1.shard, shard1)
1819 self.assertItemsEqual(shard1.labels.all(), [lumpy_label])
1820 rpc_interface.remove_board_from_shard(
1821 shard1.hostname, lumpy_label.name)
1822 host1 = models.Host.smart_get(host1.id)
1823 shard1 = models.Shard.smart_get(shard1.id)
1824 self.assertEqual(host1.shard, None)
1825 self.assertItemsEqual(shard1.labels.all(), [])
1826
1827
Allen Licdd00f22017-02-01 18:01:52 -08001828 def testCreateListShard(self):
1829 """Retrieve a list of all shards."""
1830 lumpy_label = models.Label.objects.create(name='board:lumpy',
1831 platform=True)
1832 stumpy_label = models.Label.objects.create(name='board:stumpy',
1833 platform=True)
1834 peppy_label = models.Label.objects.create(name='board:peppy',
1835 platform=True)
1836
1837 shard_id = rpc_interface.add_shard(
1838 hostname='host1', labels='board:lumpy,board:stumpy')
1839 self.assertRaises(error.RPCException,
1840 rpc_interface.add_shard,
1841 hostname='host1', labels='board:lumpy,board:stumpy')
1842 self.assertRaises(model_logic.ValidationError,
1843 rpc_interface.add_shard,
1844 hostname='host1', labels='board:peppy')
1845 shard = models.Shard.objects.get(pk=shard_id)
1846 self.assertEqual(shard.hostname, 'host1')
1847 self.assertEqual(shard.labels.values_list('pk')[0], (lumpy_label.id,))
1848 self.assertEqual(shard.labels.values_list('pk')[1], (stumpy_label.id,))
1849
1850 self.assertEqual(rpc_interface.get_shards(),
1851 [{'labels': ['board:lumpy','board:stumpy'],
1852 'hostname': 'host1',
1853 'id': 1}])
1854
1855
1856 def testAddBoardsToShard(self):
1857 """Add boards to a given shard."""
1858 shard1, host1, lumpy_label = self._createShardAndHostWithLabel()
1859 stumpy_label = models.Label.objects.create(name='board:stumpy',
1860 platform=True)
1861 shard_id = rpc_interface.add_board_to_shard(
1862 hostname='shard1', labels='board:stumpy')
1863 # Test whether raise exception when board label does not exist.
1864 self.assertRaises(models.Label.DoesNotExist,
1865 rpc_interface.add_board_to_shard,
1866 hostname='shard1', labels='board:test')
1867 # Test whether raise exception when board already sharded.
1868 self.assertRaises(error.RPCException,
1869 rpc_interface.add_board_to_shard,
1870 hostname='shard1', labels='board:lumpy')
1871 shard = models.Shard.objects.get(pk=shard_id)
1872 self.assertEqual(shard.hostname, 'shard1')
1873 self.assertEqual(shard.labels.values_list('pk')[0], (lumpy_label.id,))
1874 self.assertEqual(shard.labels.values_list('pk')[1], (stumpy_label.id,))
1875
1876 self.assertEqual(rpc_interface.get_shards(),
1877 [{'labels': ['board:lumpy','board:stumpy'],
1878 'hostname': 'shard1',
1879 'id': 1}])
1880
1881
Xixuan Wu4379e932018-01-17 10:29:50 -08001882 def testShardHeartbeatFetchHostlessJob(self):
1883 shard1, host1, label1 = self._createShardAndHostWithLabel()
1884 self._testShardHeartbeatFetchHostlessJobHelper(host1)
1885
1886
1887 def testShardHeartbeatIncorrectHosts(self):
1888 shard1, host1, label1 = self._createShardAndHostWithLabel()
1889 self._testShardHeartbeatIncorrectHostsHelper(host1)
1890
1891
1892 def testShardHeartbeatLabelRemovalRace(self):
1893 shard1, host1, label1 = self._createShardAndHostWithLabel()
1894 self._testShardHeartbeatLabelRemovalRaceHelper(shard1, host1, label1)
1895
1896
1897 def testShardRetrieveJobs(self):
1898 shard1, host1, label1 = self._createShardAndHostWithLabel()
1899 shard2, host2, label2 = self._createShardAndHostWithLabel(
1900 'shard2', 'host2', 'board:grumpy')
1901 self._testShardRetrieveJobsHelper(shard1, host1, label1,
1902 shard2, host2, label2)
1903
1904
1905 def testResendJobsAfterFailedHeartbeat(self):
1906 shard1, host1, label1 = self._createShardAndHostWithLabel()
1907 self._testResendJobsAfterFailedHeartbeatHelper(shard1, host1, label1)
1908
1909
Allen Licdd00f22017-02-01 18:01:52 -08001910 def testResendHostsAfterFailedHeartbeat(self):
Xixuan Wu4379e932018-01-17 10:29:50 -08001911 shard1, host1, label1 = self._createShardAndHostWithLabel()
1912 self._testResendHostsAfterFailedHeartbeatHelper(host1)
Allen Licdd00f22017-02-01 18:01:52 -08001913
1914
Jacob Kopczynskid6a5e912018-03-01 09:52:34 -08001915 def testShardHeartbeatBadReadonlyQuery(self):
1916 old_readonly = models.Job.FETCH_READONLY_JOBS
1917 shard1, host1, label1 = self._createShardAndHostWithLabel(
1918 host_hostname='test_host1')
1919 self._testShardHeartbeatBadReadonlyQueryHelper(shard1, host1, label1)
1920
1921
showardb6d16622009-05-26 19:35:29 +00001922if __name__ == '__main__':
1923 unittest.main()