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