blob: fb2c5466703455abfae39d17fa5ad5eff3d95fa0 [file] [log] [blame]
beeps7d8273b2013-11-06 09:44:34 -08001#!/usr/bin/python
beeps7d8273b2013-11-06 09:44:34 -08002
Richard Barnettedd0ec812016-07-13 11:40:54 -07003#pylint: disable=missing-docstring
4
Justin Giorgi67ad67d2016-06-29 14:41:04 -07005import unittest
6
beeps7d8273b2013-11-06 09:44:34 -08007import common
8from autotest_lib.frontend import setup_django_environment
9from autotest_lib.frontend.afe import frontend_test_utils
beeps7d8273b2013-11-06 09:44:34 -080010from autotest_lib.frontend.afe import models
beeps7d8273b2013-11-06 09:44:34 -080011from autotest_lib.scheduler import monitor_db_unittest
12from autotest_lib.scheduler import scheduler_models
13
14_DEBUG = False
15
16
17class AtomicGroupTest(monitor_db_unittest.DispatcherSchedulingTest):
18
19 def test_atomic_group_hosts_blocked_from_non_atomic_jobs(self):
20 # Create a job scheduled to run on label6.
21 self._create_job(metahosts=[self.label6.id])
22 self._run_scheduler()
23 # label6 only has hosts that are in atomic groups associated with it,
24 # there should be no scheduling.
25 self._check_for_extra_schedulings()
26
27
28 def test_atomic_group_hosts_blocked_from_non_atomic_jobs_explicit(self):
29 # Create a job scheduled to run on label5. This is an atomic group
30 # label but this job does not request atomic group scheduling.
31 self._create_job(metahosts=[self.label5.id])
32 self._run_scheduler()
33 # label6 only has hosts that are in atomic groups associated with it,
34 # there should be no scheduling.
35 self._check_for_extra_schedulings()
36
37
38 def test_atomic_group_scheduling_basics(self):
39 # Create jobs scheduled to run on an atomic group.
40 job_a = self._create_job(synchronous=True, metahosts=[self.label4.id],
41 atomic_group=1)
42 job_b = self._create_job(synchronous=True, metahosts=[self.label5.id],
43 atomic_group=1)
44 self._run_scheduler()
45 # atomic_group.max_number_of_machines was 2 so we should run on 2.
46 self._assert_job_scheduled_on_number_of(job_a.id, (5, 6, 7), 2)
47 self._assert_job_scheduled_on(job_b.id, 8) # label5
48 self._assert_job_scheduled_on(job_b.id, 9) # label5
49 self._check_for_extra_schedulings()
50
51 # The three host label4 atomic group still has one host available.
52 # That means a job with a synch_count of 1 asking to be scheduled on
53 # the atomic group can still use the final machine.
54 #
55 # This may seem like a somewhat odd use case. It allows the use of an
56 # atomic group as a set of machines to run smaller jobs within (a set
57 # of hosts configured for use in network tests with eachother perhaps?)
58 onehost_job = self._create_job(atomic_group=1)
59 self._run_scheduler()
60 self._assert_job_scheduled_on_number_of(onehost_job.id, (5, 6, 7), 1)
61 self._check_for_extra_schedulings()
62
63 # No more atomic groups have hosts available, no more jobs should
64 # be scheduled.
65 self._create_job(atomic_group=1)
66 self._run_scheduler()
67 self._check_for_extra_schedulings()
68
69
70 def test_atomic_group_scheduling_obeys_acls(self):
71 # Request scheduling on a specific atomic label but be denied by ACLs.
72 self._do_query('DELETE FROM afe_acl_groups_hosts '
73 'WHERE host_id in (8,9)')
74 job = self._create_job(metahosts=[self.label5.id], atomic_group=1)
75 self._run_scheduler()
76 self._check_for_extra_schedulings()
77
78
79 def test_atomic_group_scheduling_dependency_label_exclude(self):
80 # A dependency label that matches no hosts in the atomic group.
81 job_a = self._create_job(atomic_group=1)
82 job_a.dependency_labels.add(self.label3)
83 self._run_scheduler()
84 self._check_for_extra_schedulings()
85
86
87 def test_atomic_group_scheduling_metahost_dependency_label_exclude(self):
88 # A metahost and dependency label that excludes too many hosts.
89 job_b = self._create_job(synchronous=True, metahosts=[self.label4.id],
90 atomic_group=1)
91 job_b.dependency_labels.add(self.label7)
92 self._run_scheduler()
93 self._check_for_extra_schedulings()
94
95
96 def test_atomic_group_scheduling_dependency_label_match(self):
97 # A dependency label that exists on enough atomic group hosts in only
98 # one of the two atomic group labels.
99 job_c = self._create_job(synchronous=True, atomic_group=1)
100 job_c.dependency_labels.add(self.label7)
101 self._run_scheduler()
102 self._assert_job_scheduled_on_number_of(job_c.id, (8, 9), 2)
103 self._check_for_extra_schedulings()
104
105
106 def test_atomic_group_scheduling_no_metahost(self):
107 # Force it to schedule on the other group for a reliable test.
108 self._do_query('UPDATE afe_hosts SET invalid=1 WHERE id=9')
109 # An atomic job without a metahost.
110 job = self._create_job(synchronous=True, atomic_group=1)
111 self._run_scheduler()
112 self._assert_job_scheduled_on_number_of(job.id, (5, 6, 7), 2)
113 self._check_for_extra_schedulings()
114
115
116 def test_atomic_group_scheduling_partial_group(self):
117 # Make one host in labels[3] unavailable so that there are only two
118 # hosts left in the group.
119 self._do_query('UPDATE afe_hosts SET status="Repair Failed" WHERE id=5')
120 job = self._create_job(synchronous=True, metahosts=[self.label4.id],
121 atomic_group=1)
122 self._run_scheduler()
123 # Verify that it was scheduled on the 2 ready hosts in that group.
124 self._assert_job_scheduled_on(job.id, 6)
125 self._assert_job_scheduled_on(job.id, 7)
126 self._check_for_extra_schedulings()
127
128
129 def test_atomic_group_scheduling_not_enough_available(self):
130 # Mark some hosts in each atomic group label as not usable.
131 # One host running, another invalid in the first group label.
132 self._do_query('UPDATE afe_hosts SET status="Running" WHERE id=5')
133 self._do_query('UPDATE afe_hosts SET invalid=1 WHERE id=6')
134 # One host invalid in the second group label.
135 self._do_query('UPDATE afe_hosts SET invalid=1 WHERE id=9')
136 # Nothing to schedule when no group label has enough (2) good hosts..
137 self._create_job(atomic_group=1, synchronous=True)
138 self._run_scheduler()
139 # There are not enough hosts in either atomic group,
140 # No more scheduling should occur.
141 self._check_for_extra_schedulings()
142
143 # Now create an atomic job that has a synch count of 1. It should
144 # schedule on exactly one of the hosts.
145 onehost_job = self._create_job(atomic_group=1)
146 self._run_scheduler()
147 self._assert_job_scheduled_on_number_of(onehost_job.id, (7, 8), 1)
148
149
150 def test_atomic_group_scheduling_no_valid_hosts(self):
151 self._do_query('UPDATE afe_hosts SET invalid=1 WHERE id in (8,9)')
152 self._create_job(synchronous=True, metahosts=[self.label5.id],
153 atomic_group=1)
154 self._run_scheduler()
155 # no hosts in the selected group and label are valid. no schedulings.
156 self._check_for_extra_schedulings()
157
158
159 def test_atomic_group_scheduling_metahost_works(self):
160 # Test that atomic group scheduling also obeys metahosts.
161 self._create_job(metahosts=[0], atomic_group=1)
162 self._run_scheduler()
163 # There are no atomic group hosts that also have that metahost.
164 self._check_for_extra_schedulings()
165
166 job_b = self._create_job(metahosts=[self.label5.id], atomic_group=1)
167 self._run_scheduler()
168 self._assert_job_scheduled_on(job_b.id, 8)
169 self._assert_job_scheduled_on(job_b.id, 9)
170 self._check_for_extra_schedulings()
171
172
173 def test_atomic_group_skips_ineligible_hosts(self):
174 # Test hosts marked ineligible for this job are not eligible.
175 # How would this ever happen anyways?
176 job = self._create_job(metahosts=[self.label4.id], atomic_group=1)
177 models.IneligibleHostQueue.objects.create(job=job, host_id=5)
178 models.IneligibleHostQueue.objects.create(job=job, host_id=6)
179 models.IneligibleHostQueue.objects.create(job=job, host_id=7)
180 self._run_scheduler()
181 # No scheduling should occur as all desired hosts were ineligible.
182 self._check_for_extra_schedulings()
183
184
185 def test_atomic_group_scheduling_fail(self):
186 # If synch_count is > the atomic group number of machines, the job
187 # should be aborted immediately.
188 model_job = self._create_job(synchronous=True, atomic_group=1)
189 model_job.synch_count = 4
190 model_job.save()
191 job = scheduler_models.Job(id=model_job.id)
192 self._run_scheduler()
193 self._check_for_extra_schedulings()
Richard Barnettedd0ec812016-07-13 11:40:54 -0700194 queue_entries = scheduler_models.HostQueueEntry.fetch(
195 where='job_id=%d' % job.id)
beeps7d8273b2013-11-06 09:44:34 -0800196 self.assertEqual(1, len(queue_entries))
197 self.assertEqual(queue_entries[0].status,
198 models.HostQueueEntry.Status.ABORTED)
199
200
201 def test_atomic_group_no_labels_no_scheduling(self):
202 # Never schedule on atomic groups marked invalid.
203 job = self._create_job(metahosts=[self.label5.id], synchronous=True,
204 atomic_group=1)
205 # Deleting an atomic group via the frontend marks it invalid and
206 # removes all label references to the group. The job now references
207 # an invalid atomic group with no labels associated with it.
208 self.label5.atomic_group.invalid = True
209 self.label5.atomic_group.save()
210 self.label5.atomic_group = None
211 self.label5.save()
212
213 self._run_scheduler()
214 self._check_for_extra_schedulings()
215
216
217 def test_schedule_directly_on_atomic_group_host_fail(self):
218 # Scheduling a job directly on hosts in an atomic group must
219 # fail to avoid users inadvertently holding up the use of an
220 # entire atomic group by using the machines individually.
221 job = self._create_job(hosts=[5])
222 self._run_scheduler()
223 self._check_for_extra_schedulings()
224
225
226 def test_schedule_directly_on_atomic_group_host(self):
227 # Scheduling a job directly on one host in an atomic group will
228 # work when the atomic group is listed on the HQE in addition
229 # to the host (assuming the sync count is 1).
230 job = self._create_job(hosts=[5], atomic_group=1)
231 self._run_scheduler()
232 self._assert_job_scheduled_on(job.id, 5)
233 self._check_for_extra_schedulings()
234
235
236 def test_schedule_directly_on_atomic_group_hosts_sync2(self):
237 job = self._create_job(hosts=[5,8], atomic_group=1, synchronous=True)
238 self._run_scheduler()
239 self._assert_job_scheduled_on(job.id, 5)
240 self._assert_job_scheduled_on(job.id, 8)
241 self._check_for_extra_schedulings()
242
243
244 def test_schedule_directly_on_atomic_group_hosts_wrong_group(self):
245 job = self._create_job(hosts=[5,8], atomic_group=2, synchronous=True)
246 self._run_scheduler()
247 self._check_for_extra_schedulings()
248
249
250 # TODO(gps): These should probably live in their own TestCase class
251 # specific to testing HostScheduler methods directly. It was convenient
252 # to put it here for now to share existing test environment setup code.
253 def test_HostScheduler_check_atomic_group_labels(self):
254 normal_job = self._create_job(metahosts=[0])
255 atomic_job = self._create_job(atomic_group=1)
256 # Indirectly initialize the internal state of the host scheduler.
257 self._dispatcher._refresh_pending_queue_entries()
258
259 atomic_hqe = scheduler_models.HostQueueEntry.fetch(where='job_id=%d' %
260 atomic_job.id)[0]
261 normal_hqe = scheduler_models.HostQueueEntry.fetch(where='job_id=%d' %
262 normal_job.id)[0]
263
264 host_scheduler = self._dispatcher._host_scheduler
265 self.assertTrue(host_scheduler._check_atomic_group_labels(
266 [self.label4.id], atomic_hqe))
267 self.assertFalse(host_scheduler._check_atomic_group_labels(
268 [self.label4.id], normal_hqe))
269 self.assertFalse(host_scheduler._check_atomic_group_labels(
270 [self.label5.id, self.label6.id, self.label7.id], normal_hqe))
271 self.assertTrue(host_scheduler._check_atomic_group_labels(
272 [self.label4.id, self.label6.id], atomic_hqe))
273 self.assertTrue(host_scheduler._check_atomic_group_labels(
274 [self.label4.id, self.label5.id],
275 atomic_hqe))
276
277
278class OnlyIfNeededTest(monitor_db_unittest.DispatcherSchedulingTest):
279
280 def _setup_test_only_if_needed_labels(self):
281 # apply only_if_needed label3 to host1
282 models.Host.smart_get('host1').labels.add(self.label3)
283 return self._create_job_simple([1], use_metahost=True)
284
285
286 def test_only_if_needed_labels_avoids_host(self):
287 job = self._setup_test_only_if_needed_labels()
288 # if the job doesn't depend on label3, there should be no scheduling
289 self._run_scheduler()
290 self._check_for_extra_schedulings()
291
292
293 def test_only_if_needed_labels_schedules(self):
294 job = self._setup_test_only_if_needed_labels()
295 job.dependency_labels.add(self.label3)
296 self._run_scheduler()
297 self._assert_job_scheduled_on(1, 1)
298 self._check_for_extra_schedulings()
299
300
301 def test_only_if_needed_labels_via_metahost(self):
302 job = self._setup_test_only_if_needed_labels()
303 job.dependency_labels.add(self.label3)
304 # should also work if the metahost is the only_if_needed label
305 self._do_query('DELETE FROM afe_jobs_dependency_labels')
306 self._create_job(metahosts=[3])
307 self._run_scheduler()
308 self._assert_job_scheduled_on(2, 1)
309 self._check_for_extra_schedulings()
310
311
312 def test_metahosts_obey_blocks(self):
313 """
314 Metahosts can't get scheduled on hosts already scheduled for
315 that job.
316 """
317 self._create_job(metahosts=[1], hosts=[1])
318 # make the nonmetahost entry complete, so the metahost can try
319 # to get scheduled
320 self._update_hqe(set='complete = 1', where='host_id=1')
321 self._run_scheduler()
322 self._check_for_extra_schedulings()
323
324
Justin Giorgi67ad67d2016-06-29 14:41:04 -0700325if __name__ == '__main__':
326 unittest.main()
327