blob: 4da5e6b8a6def06c9d5d088a25aff8a98caef800 [file] [log] [blame]
Michael Tang97d188c2016-06-25 11:18:42 -07001# Copyright 2016 The Chromium OS Authors. All rights reserved.
J. Richard Barnetteea785362014-03-17 16:00:53 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Keith Haddow5ba5fb82016-11-09 11:39:36 -08005import __builtin__
J. Richard Barnetteea785362014-03-17 16:00:53 -07006import Queue
Michael Tang97d188c2016-06-25 11:18:42 -07007import base64
J. Richard Barnetteea785362014-03-17 16:00:53 -07008import datetime
9import logging
10import os
11import shutil
J. Richard Barnette2e443ef2014-05-20 12:31:35 -070012import signal
J. Richard Barnetteea785362014-03-17 16:00:53 -070013import sys
14import tempfile
15import time
16import unittest
17
18import mox
19
20import common
Michael Tang328073b2016-11-01 15:33:38 -070021from autotest_lib.client.common_lib import global_config, site_utils
Dan Shi1b4c7c32015-10-05 10:38:57 -070022from autotest_lib.client.common_lib import time_utils
23from autotest_lib.client.common_lib import utils
Prathmesh Prabhubeb9e012017-01-30 16:18:39 -080024from autotest_lib.site_utils import gs_offloader
25from autotest_lib.site_utils import job_directories
Ningning Xia2d981ee2016-07-06 17:59:54 -070026from autotest_lib.tko import models
Jakob Juelich24f22c22014-09-26 11:46:11 -070027
J. Richard Barnetteea785362014-03-17 16:00:53 -070028# Test value to use for `days_old`, if nothing else is required.
29_TEST_EXPIRATION_AGE = 7
30
31# When constructing sample time values for testing expiration,
32# allow this many seconds between the expiration time and the
33# current time.
34_MARGIN_SECS = 10.0
35
36
37def _get_options(argv):
38 """Helper function to exercise command line parsing.
39
40 @param argv Value of sys.argv to be parsed.
41
42 """
43 sys.argv = ['bogus.py'] + argv
44 return gs_offloader.parse_options()
45
46
Simran Basidd129972014-09-11 14:34:49 -070047class OffloaderOptionsTests(mox.MoxTestBase):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070048 """Tests for the `Offloader` constructor.
49
50 Tests that offloader instance fields are set as expected
51 for given command line options.
52
53 """
54
55 _REGULAR_ONLY = set([job_directories.RegularJobDirectory])
56 _SPECIAL_ONLY = set([job_directories.SpecialJobDirectory])
57 _BOTH = _REGULAR_ONLY | _SPECIAL_ONLY
J. Richard Barnetteea785362014-03-17 16:00:53 -070058
Jakob Juelich24f22c22014-09-26 11:46:11 -070059
Simran Basidd129972014-09-11 14:34:49 -070060 def setUp(self):
61 super(OffloaderOptionsTests, self).setUp()
62 self.mox.StubOutWithMock(utils, 'get_offload_gsuri')
Simran Basif3e305f2014-10-03 14:43:53 -070063 gs_offloader.GS_OFFLOADING_ENABLED = True
Michael Tang0df2eb42016-05-13 19:06:54 -070064 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
Simran Basidd129972014-09-11 14:34:49 -070065
Jakob Juelich24f22c22014-09-26 11:46:11 -070066
Michael Tang97d188c2016-06-25 11:18:42 -070067 def _mock_get_offload_func(self, is_moblab, multiprocessing=False,
Keith Haddow5ba5fb82016-11-09 11:39:36 -080068 pubsub_topic=None, delete_age=0):
Simran Basidd129972014-09-11 14:34:49 -070069 """Mock the process of getting the offload_dir function."""
70 if is_moblab:
71 expected_gsuri = '%sresults/%s/%s/' % (
72 global_config.global_config.get_config_value(
73 'CROS', 'image_storage_server'),
74 'Fa:ke:ma:c0:12:34', 'rand0m-uu1d')
75 else:
76 expected_gsuri = utils.DEFAULT_OFFLOAD_GSURI
77 utils.get_offload_gsuri().AndReturn(expected_gsuri)
Keith Haddow5ba5fb82016-11-09 11:39:36 -080078 offload_func = gs_offloader.get_offload_dir_func(expected_gsuri,
79 multiprocessing, delete_age, pubsub_topic)
Simran Basidd129972014-09-11 14:34:49 -070080 self.mox.StubOutWithMock(gs_offloader, 'get_offload_dir_func')
Michael Tang97d188c2016-06-25 11:18:42 -070081 gs_offloader.get_offload_dir_func(expected_gsuri, multiprocessing,
Keith Haddow5ba5fb82016-11-09 11:39:36 -080082 delete_age, pubsub_topic).AndReturn(offload_func)
Simran Basidd129972014-09-11 14:34:49 -070083 self.mox.ReplayAll()
84 return offload_func
85
Jakob Juelich24f22c22014-09-26 11:46:11 -070086
J. Richard Barnetteea785362014-03-17 16:00:53 -070087 def test_process_no_options(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070088 """Test default offloader options."""
Simran Basidd129972014-09-11 14:34:49 -070089 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070090 offloader = gs_offloader.Offloader(_get_options([]))
91 self.assertEqual(set(offloader._jobdir_classes),
92 self._REGULAR_ONLY)
93 self.assertEqual(offloader._processes, 1)
94 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -070095 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -080096 self.assertEqual(offloader._upload_age_limit, 0)
97 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -070098
Jakob Juelich24f22c22014-09-26 11:46:11 -070099
J. Richard Barnetteea785362014-03-17 16:00:53 -0700100 def test_process_all_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700101 """Test offloader handling for the --all option."""
Simran Basidd129972014-09-11 14:34:49 -0700102 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700103 offloader = gs_offloader.Offloader(_get_options(['--all']))
104 self.assertEqual(set(offloader._jobdir_classes), self._BOTH)
105 self.assertEqual(offloader._processes, 1)
106 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700107 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800108 self.assertEqual(offloader._upload_age_limit, 0)
109 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700110
Jakob Juelich24f22c22014-09-26 11:46:11 -0700111
J. Richard Barnetteea785362014-03-17 16:00:53 -0700112 def test_process_hosts_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700113 """Test offloader handling for the --hosts option."""
Simran Basidd129972014-09-11 14:34:49 -0700114 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700115 offloader = gs_offloader.Offloader(
116 _get_options(['--hosts']))
117 self.assertEqual(set(offloader._jobdir_classes),
118 self._SPECIAL_ONLY)
119 self.assertEqual(offloader._processes, 1)
120 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700121 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800122 self.assertEqual(offloader._upload_age_limit, 0)
123 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700124
Jakob Juelich24f22c22014-09-26 11:46:11 -0700125
J. Richard Barnetteea785362014-03-17 16:00:53 -0700126 def test_parallelism_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700127 """Test offloader handling for the --parallelism option."""
Simran Basidd129972014-09-11 14:34:49 -0700128 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700129 offloader = gs_offloader.Offloader(
130 _get_options(['--parallelism', '2']))
131 self.assertEqual(set(offloader._jobdir_classes),
132 self._REGULAR_ONLY)
133 self.assertEqual(offloader._processes, 2)
134 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700135 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800136 self.assertEqual(offloader._upload_age_limit, 0)
137 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700138
Jakob Juelich24f22c22014-09-26 11:46:11 -0700139
J. Richard Barnetteea785362014-03-17 16:00:53 -0700140 def test_delete_only_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700141 """Test offloader handling for the --delete_only option."""
142 offloader = gs_offloader.Offloader(
143 _get_options(['--delete_only']))
144 self.assertEqual(set(offloader._jobdir_classes),
145 self._REGULAR_ONLY)
146 self.assertEqual(offloader._processes, 1)
147 self.assertEqual(offloader._offload_func,
148 gs_offloader.delete_files)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800149 self.assertEqual(offloader._upload_age_limit, 0)
150 self.assertEqual(offloader._delete_age_limit, 0)
Michael Tang97d188c2016-06-25 11:18:42 -0700151 self.assertIsNone(offloader._pubsub_topic)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700152
Jakob Juelich24f22c22014-09-26 11:46:11 -0700153
Simran Basidf4751e2014-10-10 14:19:22 -0700154 def test_days_old_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700155 """Test offloader handling for the --days_old option."""
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800156 offload_func = self._mock_get_offload_func(False, delete_age=7)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700157 offloader = gs_offloader.Offloader(
158 _get_options(['--days_old', '7']))
159 self.assertEqual(set(offloader._jobdir_classes),
160 self._REGULAR_ONLY)
161 self.assertEqual(offloader._processes, 1)
162 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700163 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800164 self.assertEqual(offloader._upload_age_limit, 7)
165 self.assertEqual(offloader._delete_age_limit, 7)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700166
Jakob Juelich24f22c22014-09-26 11:46:11 -0700167
Simran Basidd129972014-09-11 14:34:49 -0700168 def test_moblab_gsuri_generation(self):
169 """Test offloader construction for Moblab."""
170 offload_func = self._mock_get_offload_func(True)
171 offloader = gs_offloader.Offloader(_get_options([]))
172 self.assertEqual(set(offloader._jobdir_classes),
173 self._REGULAR_ONLY)
174 self.assertEqual(offloader._processes, 1)
175 self.assertEqual(offloader._offload_func,
176 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800177 self.assertEqual(offloader._upload_age_limit, 0)
178 self.assertEqual(offloader._delete_age_limit, 0)
Simran Basidd129972014-09-11 14:34:49 -0700179
J. Richard Barnetteea785362014-03-17 16:00:53 -0700180
Simran Basif3e305f2014-10-03 14:43:53 -0700181 def test_globalconfig_offloading_flag(self):
182 """Test enabling of --delete_only via global_config."""
183 gs_offloader.GS_OFFLOADING_ENABLED = False
184 offloader = gs_offloader.Offloader(
185 _get_options([]))
186 self.assertEqual(offloader._offload_func,
187 gs_offloader.delete_files)
188
Michael Tang0df2eb42016-05-13 19:06:54 -0700189 def test_offloader_multiprocessing_flag_set(self):
190 """Test multiprocessing is set."""
191 offload_func = self._mock_get_offload_func(True, True)
192 offloader = gs_offloader.Offloader(_get_options(['-m']))
193 self.assertEqual(offloader._offload_func,
194 offload_func)
195 self.mox.VerifyAll()
196
197 def test_offloader_multiprocessing_flag_not_set_default_false(self):
198 """Test multiprocessing is set."""
199 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
200 offload_func = self._mock_get_offload_func(True, False)
201 offloader = gs_offloader.Offloader(_get_options([]))
202 self.assertEqual(offloader._offload_func,
203 offload_func)
204 self.mox.VerifyAll()
205
206 def test_offloader_multiprocessing_flag_not_set_default_true(self):
207 """Test multiprocessing is set."""
208 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = True
209 offload_func = self._mock_get_offload_func(True, True)
210 offloader = gs_offloader.Offloader(_get_options([]))
211 self.assertEqual(offloader._offload_func,
212 offload_func)
213 self.mox.VerifyAll()
214
Michael Tang97d188c2016-06-25 11:18:42 -0700215 def test_offloader_pubsub_topic_not_set(self):
216 """Test multiprocessing is set."""
217 offload_func = self._mock_get_offload_func(True, False)
218 offloader = gs_offloader.Offloader(_get_options([]))
219 self.assertEqual(offloader._offload_func,
220 offload_func)
221 self.mox.VerifyAll()
222
223 def test_offloader_pubsub_topic_set(self):
224 """Test multiprocessing is set."""
225 offload_func = self._mock_get_offload_func(True, False, 'test-topic')
226 offloader = gs_offloader.Offloader(_get_options(['-t', 'test-topic']))
227 self.assertEqual(offloader._offload_func,
228 offload_func)
229 self.mox.VerifyAll()
230
Simran Basif3e305f2014-10-03 14:43:53 -0700231
J. Richard Barnetteea785362014-03-17 16:00:53 -0700232def _make_timestamp(age_limit, is_expired):
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800233 """Create a timestamp for use by `job_directories.is_job_expired()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -0700234
235 The timestamp will meet the syntactic requirements for
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800236 timestamps used as input to `is_job_expired()`. If
J. Richard Barnetteea785362014-03-17 16:00:53 -0700237 `is_expired` is true, the timestamp will be older than
238 `age_limit` days before the current time; otherwise, the
239 date will be younger.
240
241 @param age_limit The number of days before expiration of the
242 target timestamp.
243 @param is_expired Whether the timestamp should be expired
244 relative to `age_limit`.
245
246 """
247 seconds = -_MARGIN_SECS
248 if is_expired:
249 seconds = -seconds
250 delta = datetime.timedelta(days=age_limit, seconds=seconds)
251 reference_time = datetime.datetime.now() - delta
Dan Shidfea3682014-08-10 23:38:40 -0700252 return reference_time.strftime(time_utils.TIME_FMT)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700253
254
255class JobExpirationTests(unittest.TestCase):
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800256 """Tests to exercise `job_directories.is_job_expired()`."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700257
258 def test_expired(self):
259 """Test detection of an expired job."""
260 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, True)
261 self.assertTrue(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800262 job_directories.is_job_expired(
J. Richard Barnetteea785362014-03-17 16:00:53 -0700263 _TEST_EXPIRATION_AGE, timestamp))
264
265
266 def test_alive(self):
267 """Test detection of a job that's not expired."""
268 # N.B. This test may fail if its run time exceeds more than
269 # about _MARGIN_SECS seconds.
270 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, False)
271 self.assertFalse(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800272 job_directories.is_job_expired(
J. Richard Barnetteea785362014-03-17 16:00:53 -0700273 _TEST_EXPIRATION_AGE, timestamp))
274
275
276class _MockJobDirectory(job_directories._JobDirectory):
277 """Subclass of `_JobDirectory` used as a helper for tests."""
278
279 GLOB_PATTERN = '[0-9]*-*'
280
Jakob Juelich24f22c22014-09-26 11:46:11 -0700281
J. Richard Barnetteea785362014-03-17 16:00:53 -0700282 def __init__(self, resultsdir):
283 """Create new job in initial state."""
284 super(_MockJobDirectory, self).__init__(resultsdir)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700285 self._timestamp = None
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800286 self.queue_args = [resultsdir, os.path.dirname(resultsdir), self._timestamp]
J. Richard Barnetteea785362014-03-17 16:00:53 -0700287
Jakob Juelich24f22c22014-09-26 11:46:11 -0700288
J. Richard Barnetteea785362014-03-17 16:00:53 -0700289 def get_timestamp_if_finished(self):
290 return self._timestamp
291
Jakob Juelich24f22c22014-09-26 11:46:11 -0700292
J. Richard Barnetteea785362014-03-17 16:00:53 -0700293 def set_finished(self, days_old):
294 """Make this job appear to be finished.
295
296 After calling this function, calls to `enqueue_offload()`
297 will find this job as finished, but not expired and ready
298 for offload. Note that when `days_old` is 0,
299 `enqueue_offload()` will treat a finished job as eligible
300 for offload.
301
302 @param days_old The value of the `days_old` parameter that
303 will be passed to `enqueue_offload()` for
304 testing.
305
306 """
307 self._timestamp = _make_timestamp(days_old, False)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800308 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700309
Jakob Juelich24f22c22014-09-26 11:46:11 -0700310
J. Richard Barnetteea785362014-03-17 16:00:53 -0700311 def set_expired(self, days_old):
312 """Make this job eligible to be offloaded.
313
314 After calling this function, calls to `offload` will attempt
315 to offload this job.
316
317 @param days_old The value of the `days_old` parameter that
318 will be passed to `enqueue_offload()` for
319 testing.
320
321 """
322 self._timestamp = _make_timestamp(days_old, True)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800323 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700324
Jakob Juelich24f22c22014-09-26 11:46:11 -0700325
J. Richard Barnetteea785362014-03-17 16:00:53 -0700326 def set_incomplete(self):
327 """Make this job appear to have failed offload just once."""
328 self._offload_count += 1
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700329 self._first_offload_start = time.time()
J. Richard Barnetteea785362014-03-17 16:00:53 -0700330 if not os.path.isdir(self._dirname):
331 os.mkdir(self._dirname)
332
Jakob Juelich24f22c22014-09-26 11:46:11 -0700333
J. Richard Barnetteea785362014-03-17 16:00:53 -0700334 def set_reportable(self):
335 """Make this job be reportable."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700336 self.set_incomplete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700337 self._offload_count += 1
J. Richard Barnetteea785362014-03-17 16:00:53 -0700338
Jakob Juelich24f22c22014-09-26 11:46:11 -0700339
J. Richard Barnetteea785362014-03-17 16:00:53 -0700340 def set_complete(self):
341 """Make this job be completed."""
342 self._offload_count += 1
343 if os.path.isdir(self._dirname):
344 os.rmdir(self._dirname)
345
346
Simran Basi1e10e922015-04-16 15:09:56 -0700347 def process_gs_instructions(self):
348 """Always still offload the job directory."""
349 return True
350
351
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700352class CommandListTests(unittest.TestCase):
353 """Tests for `get_cmd_list()`."""
354
MK Ryue93c8572015-08-11 11:53:00 -0700355 def _command_list_assertions(self, job, use_rsync=True, multi=False):
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700356 """Call `get_cmd_list()` and check the return value.
357
358 Check the following assertions:
359 * The command name (argv[0]) is 'gsutil'.
MK Ryue93c8572015-08-11 11:53:00 -0700360 * '-m' option (argv[1]) is on when the argument, multi, is True.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700361 * The arguments contain the 'cp' subcommand.
362 * The next-to-last argument (the source directory) is the
363 job's `queue_args[0]`.
Simran Basidd129972014-09-11 14:34:49 -0700364 * The last argument (the destination URL) is the job's
365 'queue_args[1]'.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700366
367 @param job A job with properly calculated arguments to
368 `get_cmd_list()`
MK Ryue93c8572015-08-11 11:53:00 -0700369 @param use_rsync True when using 'rsync'. False when using 'cp'.
370 @param multi True when using '-m' option for gsutil.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700371
372 """
Jakob Juelich24f22c22014-09-26 11:46:11 -0700373 test_bucket_uri = 'gs://a-test-bucket'
374
375 gs_offloader.USE_RSYNC_ENABLED = use_rsync
376
377 command = gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700378 multi, job.queue_args[0],
379 os.path.join(test_bucket_uri, job.queue_args[1]))
Jakob Juelich24f22c22014-09-26 11:46:11 -0700380
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700381 self.assertEqual(command[0], 'gsutil')
MK Ryue93c8572015-08-11 11:53:00 -0700382 if multi:
383 self.assertEqual(command[1], '-m')
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700384 self.assertEqual(command[-2], job.queue_args[0])
Jakob Juelich24f22c22014-09-26 11:46:11 -0700385
386 if use_rsync:
387 self.assertTrue('rsync' in command)
388 self.assertEqual(command[-1],
389 os.path.join(test_bucket_uri, job.queue_args[0]))
390 else:
391 self.assertTrue('cp' in command)
392 self.assertEqual(command[-1],
393 os.path.join(test_bucket_uri, job.queue_args[1]))
394
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700395
396 def test_get_cmd_list_regular(self):
397 """Test `get_cmd_list()` as for a regular job."""
398 job = _MockJobDirectory('118-debug')
399 self._command_list_assertions(job)
400
Jakob Juelich24f22c22014-09-26 11:46:11 -0700401
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700402 def test_get_cmd_list_special(self):
403 """Test `get_cmd_list()` as for a special job."""
404 job = _MockJobDirectory('hosts/host1/118-reset')
405 self._command_list_assertions(job)
406
407
Jakob Juelich24f22c22014-09-26 11:46:11 -0700408 def test_get_cmd_list_regular_no_rsync(self):
409 """Test `get_cmd_list()` as for a regular job."""
410 job = _MockJobDirectory('118-debug')
411 self._command_list_assertions(job, use_rsync=False)
412
413
414 def test_get_cmd_list_special_no_rsync(self):
415 """Test `get_cmd_list()` as for a special job."""
416 job = _MockJobDirectory('hosts/host1/118-reset')
417 self._command_list_assertions(job, use_rsync=False)
418
419
MK Ryue93c8572015-08-11 11:53:00 -0700420 def test_get_cmd_list_regular_multi(self):
421 """Test `get_cmd_list()` as for a regular job with True multi."""
422 job = _MockJobDirectory('118-debug')
423 self._command_list_assertions(job, multi=True)
424
425
426 def test_get_cmd_list_special_multi(self):
427 """Test `get_cmd_list()` as for a special job with True multi."""
428 job = _MockJobDirectory('hosts/host1/118-reset')
429 self._command_list_assertions(job, multi=True)
430
431
Michael Tang97d188c2016-06-25 11:18:42 -0700432class PubSubTest(mox.MoxTestBase):
433 """Test the test result notifcation data structure."""
434
435 def test_create_test_result_notification(self):
436 """Tests the test result notification message."""
Michael Tang328073b2016-11-01 15:33:38 -0700437 self.mox.StubOutWithMock(site_utils, 'get_moblab_id')
438 self.mox.StubOutWithMock(site_utils,
439 'get_default_interface_mac_address')
440 site_utils.get_default_interface_mac_address().AndReturn(
441 '1c:dc:d1:11:01:e1')
442 site_utils.get_moblab_id().AndReturn(
443 'c8386d92-9ad1-11e6-80f5-111111111111')
444 self.mox.ReplayAll()
Michael Tangb45c7232016-11-14 21:40:43 -0800445 msg = gs_offloader._create_test_result_notification(
446 'gs://test_bucket', '123-moblab')
Michael Tang97d188c2016-06-25 11:18:42 -0700447 self.assertEquals(base64.b64encode(
448 gs_offloader.NEW_TEST_RESULT_MESSAGE), msg['data'])
Michael Tang328073b2016-11-01 15:33:38 -0700449 self.assertEquals(
450 gs_offloader.NOTIFICATION_VERSION,
451 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_VERSION])
452 self.assertEquals(
453 '1c:dc:d1:11:01:e1',
454 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_MOBLAB_MAC])
455 self.assertEquals(
456 'c8386d92-9ad1-11e6-80f5-111111111111',
457 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_MOBLAB_ID])
458 self.assertEquals(
Michael Tangb45c7232016-11-14 21:40:43 -0800459 'gs://test_bucket/123-moblab',
Michael Tang328073b2016-11-01 15:33:38 -0700460 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_GCS_URI])
461 self.mox.VerifyAll()
Michael Tang97d188c2016-06-25 11:18:42 -0700462
463
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700464class _MockJob(object):
465 """Class to mock the return value of `AFE.get_jobs()`."""
466 def __init__(self, created):
467 self.created_on = created
468
469
470class _MockHostQueueEntry(object):
471 """Class to mock the return value of `AFE.get_host_queue_entries()`."""
472 def __init__(self, finished):
473 self.finished_on = finished
474
475
476class _MockSpecialTask(object):
477 """Class to mock the return value of `AFE.get_special_tasks()`."""
478 def __init__(self, finished):
479 self.time_finished = finished
480
481
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700482class JobDirectorySubclassTests(mox.MoxTestBase):
483 """Test specific to RegularJobDirectory and SpecialJobDirectory.
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700484
485 This provides coverage for the implementation in both
486 RegularJobDirectory and SpecialJobDirectory.
487
488 """
489
490 def setUp(self):
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700491 super(JobDirectorySubclassTests, self).setUp()
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700492 self.mox.StubOutWithMock(job_directories._AFE, 'get_jobs')
493 self.mox.StubOutWithMock(job_directories._AFE,
494 'get_host_queue_entries')
495 self.mox.StubOutWithMock(job_directories._AFE,
496 'get_special_tasks')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700497
Jakob Juelich24f22c22014-09-26 11:46:11 -0700498
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700499 def test_regular_job_fields(self):
500 """Test the constructor for `RegularJobDirectory`.
501
502 Construct a regular job, and assert that the `_dirname`
503 and `_id` attributes are set as expected.
504
505 """
506 resultsdir = '118-fubar'
507 job = job_directories.RegularJobDirectory(resultsdir)
508 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700509 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700510
Jakob Juelich24f22c22014-09-26 11:46:11 -0700511
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700512 def test_special_job_fields(self):
513 """Test the constructor for `SpecialJobDirectory`.
514
515 Construct a special job, and assert that the `_dirname`
516 and `_id` attributes are set as expected.
517
518 """
519 destdir = 'hosts/host1'
520 resultsdir = destdir + '/118-reset'
521 job = job_directories.SpecialJobDirectory(resultsdir)
522 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700523 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700524
Jakob Juelich24f22c22014-09-26 11:46:11 -0700525
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700526 def _check_finished_job(self, jobtime, hqetimes, expected):
527 """Mock and test behavior of a finished job.
528
529 Initialize the mocks for a call to
530 `get_timestamp_if_finished()`, then simulate one call.
531 Assert that the returned timestamp matches the passed
532 in expected value.
533
534 @param jobtime Time used to construct a _MockJob object.
535 @param hqetimes List of times used to construct
536 _MockHostQueueEntry objects.
537 @param expected Expected time to be returned by
538 get_timestamp_if_finished
539
540 """
541 job = job_directories.RegularJobDirectory('118-fubar')
542 job_directories._AFE.get_jobs(
543 id=job._id, finished=True).AndReturn(
544 [_MockJob(jobtime)])
545 job_directories._AFE.get_host_queue_entries(
546 finished_on__isnull=False,
547 job_id=job._id).AndReturn(
548 [_MockHostQueueEntry(t) for t in hqetimes])
549 self.mox.ReplayAll()
550 self.assertEqual(expected, job.get_timestamp_if_finished())
551 self.mox.VerifyAll()
552
553
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700554 def test_finished_regular_job(self):
555 """Test getting the timestamp for a finished regular job.
556
557 Tests the return value for
558 `RegularJobDirectory.get_timestamp_if_finished()` when
559 the AFE indicates the job is finished.
560
561 """
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700562 created_timestamp = _make_timestamp(1, True)
563 hqe_timestamp = _make_timestamp(0, True)
564 self._check_finished_job(created_timestamp,
565 [hqe_timestamp],
566 hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700567
Jakob Juelich24f22c22014-09-26 11:46:11 -0700568
Simran Basifb98e462014-08-18 12:35:44 -0700569 def test_finished_regular_job_multiple_hqes(self):
570 """Test getting the timestamp for a regular job with multiple hqes.
571
572 Tests the return value for
573 `RegularJobDirectory.get_timestamp_if_finished()` when
574 the AFE indicates the job is finished and the job has multiple host
575 queue entries.
576
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700577 Tests that the returned timestamp is the latest timestamp in
578 the list of HQEs, regardless of the returned order.
579
Simran Basifb98e462014-08-18 12:35:44 -0700580 """
Simran Basifb98e462014-08-18 12:35:44 -0700581 created_timestamp = _make_timestamp(2, True)
582 older_hqe_timestamp = _make_timestamp(1, True)
583 newer_hqe_timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700584 hqe_list = [older_hqe_timestamp,
585 newer_hqe_timestamp]
586 self._check_finished_job(created_timestamp,
587 hqe_list,
588 newer_hqe_timestamp)
589 self.mox.ResetAll()
590 hqe_list.reverse()
591 self._check_finished_job(created_timestamp,
592 hqe_list,
593 newer_hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700594
Jakob Juelich24f22c22014-09-26 11:46:11 -0700595
Simran Basifb98e462014-08-18 12:35:44 -0700596 def test_finished_regular_job_null_finished_times(self):
597 """Test getting the timestamp for an aborted regular job.
598
599 Tests the return value for
600 `RegularJobDirectory.get_timestamp_if_finished()` when
601 the AFE indicates the job is finished and the job has aborted host
602 queue entries.
603
604 """
Simran Basifb98e462014-08-18 12:35:44 -0700605 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700606 self._check_finished_job(timestamp, [], timestamp)
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700607
Jakob Juelich24f22c22014-09-26 11:46:11 -0700608
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700609 def test_unfinished_regular_job(self):
610 """Test getting the timestamp for an unfinished regular job.
611
612 Tests the return value for
613 `RegularJobDirectory.get_timestamp_if_finished()` when
614 the AFE indicates the job is not finished.
615
616 """
617 job = job_directories.RegularJobDirectory('118-fubar')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700618 job_directories._AFE.get_jobs(
619 id=job._id, finished=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700620 self.mox.ReplayAll()
621 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700622 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700623
Jakob Juelich24f22c22014-09-26 11:46:11 -0700624
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700625 def test_finished_special_job(self):
626 """Test getting the timestamp for a finished special job.
627
628 Tests the return value for
629 `SpecialJobDirectory.get_timestamp_if_finished()` when
630 the AFE indicates the job is finished.
631
632 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700633 job = job_directories.SpecialJobDirectory(
634 'hosts/host1/118-reset')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700635 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700636 job_directories._AFE.get_special_tasks(
637 id=job._id, is_complete=True).AndReturn(
638 [_MockSpecialTask(timestamp)])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700639 self.mox.ReplayAll()
640 self.assertEqual(timestamp,
641 job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700642 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700643
Jakob Juelich24f22c22014-09-26 11:46:11 -0700644
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700645 def test_unfinished_special_job(self):
646 """Test getting the timestamp for an unfinished special job.
647
648 Tests the return value for
649 `SpecialJobDirectory.get_timestamp_if_finished()` when
650 the AFE indicates the job is not finished.
651
652 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700653 job = job_directories.SpecialJobDirectory(
654 'hosts/host1/118-reset')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700655 job_directories._AFE.get_special_tasks(
656 id=job._id, is_complete=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700657 self.mox.ReplayAll()
658 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700659 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700660
661
J. Richard Barnetteea785362014-03-17 16:00:53 -0700662class _TempResultsDirTestBase(mox.MoxTestBase):
663 """Base class for tests using a temporary results directory."""
664
J. Richard Barnette08800322014-05-16 14:49:46 -0700665 REGULAR_JOBLIST = [
666 '111-fubar', '112-fubar', '113-fubar', '114-snafu']
667 HOST_LIST = ['host1', 'host2', 'host3']
668 SPECIAL_JOBLIST = [
669 'hosts/host1/333-reset', 'hosts/host1/334-reset',
670 'hosts/host2/444-reset', 'hosts/host3/555-reset']
671
Jakob Juelich24f22c22014-09-26 11:46:11 -0700672
J. Richard Barnetteea785362014-03-17 16:00:53 -0700673 def setUp(self):
674 super(_TempResultsDirTestBase, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -0700675 self._resultsroot = tempfile.mkdtemp()
676 self._cwd = os.getcwd()
677 os.chdir(self._resultsroot)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700678
Jakob Juelich24f22c22014-09-26 11:46:11 -0700679
J. Richard Barnetteea785362014-03-17 16:00:53 -0700680 def tearDown(self):
J. Richard Barnette08800322014-05-16 14:49:46 -0700681 os.chdir(self._cwd)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700682 shutil.rmtree(self._resultsroot)
683 super(_TempResultsDirTestBase, self).tearDown()
684
Jakob Juelich24f22c22014-09-26 11:46:11 -0700685
J. Richard Barnetteea785362014-03-17 16:00:53 -0700686 def make_job(self, jobdir):
687 """Create a job with results in `self._resultsroot`.
688
689 @param jobdir Name of the subdirectory to be created in
690 `self._resultsroot`.
691
692 """
J. Richard Barnette08800322014-05-16 14:49:46 -0700693 os.mkdir(jobdir)
694 return _MockJobDirectory(jobdir)
695
Jakob Juelich24f22c22014-09-26 11:46:11 -0700696
J. Richard Barnette08800322014-05-16 14:49:46 -0700697 def make_job_hierarchy(self):
698 """Create a sample hierarchy of job directories.
699
700 `self.REGULAR_JOBLIST` is a list of directories for regular
701 jobs to be created; `self.SPECIAL_JOBLIST` is a list of
702 directories for special jobs to be created.
703
704 """
705 for d in self.REGULAR_JOBLIST:
706 os.mkdir(d)
707 hostsdir = 'hosts'
708 os.mkdir(hostsdir)
709 for host in self.HOST_LIST:
710 os.mkdir(os.path.join(hostsdir, host))
711 for d in self.SPECIAL_JOBLIST:
712 os.mkdir(d)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700713
714
Prathmesh Prabhu80dfb1e2017-01-30 18:01:29 -0800715class FailedOffloadsLogTest(_TempResultsDirTestBase):
716 """Test the formatting of failed offloads log file."""
717 # Below is partial sample of a failed offload log file. This text is
718 # deliberately hard-coded and then parsed to create the test data; the idea
719 # is to make sure the actual text format will be reviewed by a human being.
720 #
721 # first offload count directory
722 # --+----1----+---- ----+ ----+----1----+----2----+----3
723 _SAMPLE_DIRECTORIES_REPORT = '''\
724 =================== ====== ==============================
725 2014-03-14 15:09:26 1 118-fubar
726 2014-03-14 15:19:23 2 117-fubar
727 2014-03-14 15:29:20 6 116-fubar
728 2014-03-14 15:39:17 24 115-fubar
729 2014-03-14 15:49:14 120 114-fubar
730 2014-03-14 15:59:11 720 113-fubar
731 2014-03-14 16:09:08 5040 112-fubar
732 2014-03-14 16:19:05 40320 111-fubar
733 '''
734
735 def setUp(self):
736 super(FailedOffloadsLogTest, self).setUp()
737 self._offloader = gs_offloader.Offloader(_get_options([]))
738 self._joblist = []
739 for line in self._SAMPLE_DIRECTORIES_REPORT.split('\n')[1 : -1]:
740 date_, time_, count, dir_ = line.split()
741 job = _MockJobDirectory(dir_)
742 job._offload_count = int(count)
743 timestruct = time.strptime("%s %s" % (date_, time_),
744 gs_offloader.FAILED_OFFLOADS_TIME_FORMAT)
745 job._first_offload_start = time.mktime(timestruct)
746 # enter the jobs in reverse order, to make sure we
747 # test that the output will be sorted.
748 self._joblist.insert(0, job)
749
750
751 def assert_report_well_formatted(self, report_file):
752 with open(report_file, 'r') as f:
753 report_lines = f.read().split()
754
755 for end_of_header_index in range(len(report_lines)):
756 if report_lines[end_of_header_index].startswith('=='):
757 break
758 self.assertLess(end_of_header_index, len(report_lines),
759 'Failed to find end-of-header marker in the report')
760
761 relevant_lines = report_lines[end_of_header_index:]
762 expected_lines = self._SAMPLE_DIRECTORIES_REPORT.split()
763 self.assertListEqual(relevant_lines, expected_lines)
764
765
766 def test_failed_offload_log_format(self):
767 """Trigger an e-mail report and check its contents."""
768 log_file = os.path.join(self._resultsroot, 'failed_log')
769 report = self._offloader._log_failed_jobs_locally(self._joblist,
770 log_file=log_file)
771 self.assert_report_well_formatted(log_file)
772
773
774 def test_failed_offload_file_overwrite(self):
775 """Verify that we can saefly overwrite the log file."""
776 log_file = os.path.join(self._resultsroot, 'failed_log')
777 with open(log_file, 'w') as f:
778 f.write('boohoohoo')
779 report = self._offloader._log_failed_jobs_locally(self._joblist,
780 log_file=log_file)
781 self.assert_report_well_formatted(log_file)
782
783
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700784class OffloadDirectoryTests(_TempResultsDirTestBase):
785 """Tests for `offload_dir()`."""
786
787 def setUp(self):
788 super(OffloadDirectoryTests, self).setUp()
789 # offload_dir() logs messages; silence them.
790 self._saved_loglevel = logging.getLogger().getEffectiveLevel()
791 logging.getLogger().setLevel(logging.CRITICAL+1)
792 self._job = self.make_job(self.REGULAR_JOBLIST[0])
793 self.mox.StubOutWithMock(gs_offloader, 'get_cmd_list')
794 self.mox.StubOutWithMock(signal, 'alarm')
Ningning Xia2d981ee2016-07-06 17:59:54 -0700795 self.mox.StubOutWithMock(models.test, 'parse_job_keyval')
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700796
Jakob Juelich24f22c22014-09-26 11:46:11 -0700797
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700798 def tearDown(self):
799 logging.getLogger().setLevel(self._saved_loglevel)
800 super(OffloadDirectoryTests, self).tearDown()
801
Ningning Xia42111242016-06-15 14:35:58 -0700802 def _mock_upload_testresult_files(self):
803 self.mox.StubOutWithMock(gs_offloader, 'upload_testresult_files')
804 gs_offloader.upload_testresult_files(
805 mox.IgnoreArg(),mox.IgnoreArg()).AndReturn(None)
Jakob Juelich24f22c22014-09-26 11:46:11 -0700806
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800807 def _mock_create_marker_file(self):
808 self.mox.StubOutWithMock(__builtin__, 'open')
809 mock_marker_file = self.mox.CreateMock(file)
810 open(mox.IgnoreArg(), 'a').AndReturn(mock_marker_file)
811 mock_marker_file.close()
812
813
814 def _mock_offload_dir_calls(self, command, queue_args,
815 marker_initially_exists=False,
816 marker_eventually_exists=True):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700817 """Mock out the calls needed by `offload_dir()`.
818
819 This covers only the calls made when there is no timeout.
820
821 @param command Command list to be returned by the mocked
822 call to `get_cmd_list()`.
823
824 """
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800825 self.mox.StubOutWithMock(os.path, 'isfile')
826 os.path.isfile(mox.IgnoreArg()).AndReturn(marker_initially_exists)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700827 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
Simran Basidd129972014-09-11 14:34:49 -0700828 command.append(queue_args[0])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700829 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700830 False, queue_args[0],
831 '%s%s' % (utils.DEFAULT_OFFLOAD_GSURI,
832 queue_args[1])).AndReturn(command)
Ningning Xia42111242016-06-15 14:35:58 -0700833 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700834 signal.alarm(0)
835 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800836 os.path.isfile(mox.IgnoreArg()).AndReturn(marker_eventually_exists)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700837
Jakob Juelich24f22c22014-09-26 11:46:11 -0700838
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800839 def _run_offload_dir(self, should_succeed, delete_age):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700840 """Make one call to `offload_dir()`.
841
842 The caller ensures all mocks are set up already.
843
844 @param should_succeed True iff the call to `offload_dir()`
845 is expected to succeed and remove the
846 offloaded job directory.
847
848 """
849 self.mox.ReplayAll()
Simran Basidd129972014-09-11 14:34:49 -0700850 gs_offloader.get_offload_dir_func(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800851 utils.DEFAULT_OFFLOAD_GSURI, False, delete_age)(
MK Ryue93c8572015-08-11 11:53:00 -0700852 self._job.queue_args[0],
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800853 self._job.queue_args[1],
854 self._job.queue_args[2])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700855 self.mox.VerifyAll()
856 self.assertEqual(not should_succeed,
857 os.path.isdir(self._job.queue_args[0]))
858
Jakob Juelich24f22c22014-09-26 11:46:11 -0700859
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700860 def test_offload_success(self):
861 """Test that `offload_dir()` can succeed correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700862 self._mock_offload_dir_calls(['test', '-d'],
863 self._job.queue_args)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800864 self._mock_create_marker_file()
865 self._run_offload_dir(True, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700866
Jakob Juelich24f22c22014-09-26 11:46:11 -0700867
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700868 def test_offload_failure(self):
869 """Test that `offload_dir()` can fail correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700870 self._mock_offload_dir_calls(['test', '!', '-d'],
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800871 self._job.queue_args,
872 marker_eventually_exists=False)
873 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700874
Jakob Juelich24f22c22014-09-26 11:46:11 -0700875
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700876 def test_offload_timeout_early(self):
877 """Test that `offload_dir()` times out correctly.
878
879 This test triggers timeout at the earliest possible moment,
880 at the first call to set the timeout alarm.
881
882 """
Ningning Xia42111242016-06-15 14:35:58 -0700883 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700884 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS).AndRaise(
885 gs_offloader.TimeoutException('fubar'))
886 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800887 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700888
Jakob Juelich24f22c22014-09-26 11:46:11 -0700889
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700890 def test_offload_timeout_late(self):
891 """Test that `offload_dir()` times out correctly.
892
893 This test triggers timeout at the latest possible moment, at
894 the call to clear the timeout alarm.
895
896 """
897 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
898 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700899 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700900 ['test', '-d', self._job.queue_args[0]])
Ningning Xia42111242016-06-15 14:35:58 -0700901 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700902 signal.alarm(0).AndRaise(
903 gs_offloader.TimeoutException('fubar'))
904 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800905 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700906
907
Dan Shiaffb9222015-04-15 17:05:47 -0700908 def test_sanitize_dir(self):
909 """Test that folder/file name with invalid character can be corrected.
910 """
911 results_folder = tempfile.mkdtemp()
912 invalid_chars = '_'.join(gs_offloader.INVALID_GS_CHARS)
913 invalid_files = []
914 invalid_folder = os.path.join(
915 results_folder,
916 'invalid_name_folder_%s' % invalid_chars)
917 invalid_files.append(os.path.join(
918 invalid_folder,
919 'invalid_name_file_%s' % invalid_chars))
920 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
921 for c in range(r[0], r[1]+1):
922 # NULL cannot be in file name.
923 if c != 0:
924 invalid_files.append(os.path.join(
925 invalid_folder,
926 'invalid_name_file_%s' % chr(c)))
927 good_folder = os.path.join(results_folder, 'valid_name_folder')
928 good_file = os.path.join(good_folder, 'valid_name_file')
929 for folder in [invalid_folder, good_folder]:
930 os.makedirs(folder)
931 for f in invalid_files + [good_file]:
932 with open(f, 'w'):
933 pass
934 gs_offloader.sanitize_dir(results_folder)
935 for _, dirs, files in os.walk(results_folder):
936 for name in dirs + files:
937 self.assertEqual(name, gs_offloader.get_sanitized_name(name))
938 for c in name:
939 self.assertFalse(c in gs_offloader.INVALID_GS_CHARS)
940 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
941 self.assertFalse(ord(c) >= r[0] and ord(c) <= r[1])
942 self.assertTrue(os.path.exists(good_file))
943 shutil.rmtree(results_folder)
944
945
Dan Shi1b4c7c32015-10-05 10:38:57 -0700946 def check_limit_file_count(self, is_test_job=True):
947 """Test that folder with too many files can be compressed.
948
949 @param is_test_job: True to check the method with test job result
950 folder. Set to False for special task folder.
951 """
952 results_folder = tempfile.mkdtemp()
953 host_folder = os.path.join(
954 results_folder,
955 'lab1-host1' if is_test_job else 'hosts/lab1-host1/1-repair')
956 debug_folder = os.path.join(host_folder, 'debug')
957 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
958 for folder in [debug_folder, sysinfo_folder]:
959 os.makedirs(folder)
960 for i in range(10):
961 with open(os.path.join(folder, str(i)), 'w') as f:
962 f.write('test')
963
964 gs_offloader.MAX_FILE_COUNT = 100
965 gs_offloader.limit_file_count(
966 results_folder if is_test_job else host_folder)
967 self.assertTrue(os.path.exists(sysinfo_folder))
968
969 gs_offloader.MAX_FILE_COUNT = 10
970 gs_offloader.limit_file_count(
971 results_folder if is_test_job else host_folder)
972 self.assertFalse(os.path.exists(sysinfo_folder))
973 self.assertTrue(os.path.exists(sysinfo_folder + '.tgz'))
974 self.assertTrue(os.path.exists(debug_folder))
975
976 shutil.rmtree(results_folder)
977
978
979 def test_limit_file_count(self):
980 """Test that folder with too many files can be compressed.
981 """
982 self.check_limit_file_count(is_test_job=True)
983 self.check_limit_file_count(is_test_job=False)
984
Ningning Xia2d88eec2016-07-25 23:18:46 -0700985
Ningning Xia8db632f2016-08-19 11:01:35 -0700986 def test_is_valid_result(self):
987 """Test _is_valid_result."""
Ningning Xia21922c82016-07-29 11:03:15 -0700988 release_build = 'veyron_minnie-cheets-release/R52-8248.0.0'
989 pfq_build = 'cyan-cheets-android-pfq/R54-8623.0.0-rc1'
990 trybot_build = 'trybot-samus-release/R54-8640.0.0-b5092'
991 trybot_2_build = 'trybot-samus-pfq/R54-8640.0.0-b5092'
992 release_2_build = 'test-trybot-release/R54-8640.0.0-b5092'
Ningning Xia8db632f2016-08-19 11:01:35 -0700993 self.assertTrue(gs_offloader._is_valid_result(
994 release_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Rohit Makasana6384c102016-10-21 17:09:47 -0700995 self.assertTrue(gs_offloader._is_valid_result(
996 release_build, gs_offloader.CTS_RESULT_PATTERN, 'test_that_wrapper'))
Ningning Xia8db632f2016-08-19 11:01:35 -0700997 self.assertFalse(gs_offloader._is_valid_result(
998 release_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-bvt-cq'))
999 self.assertTrue(gs_offloader._is_valid_result(
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001000 release_build, gs_offloader.CTS_V2_RESULT_PATTERN, 'arc-gts'))
Ningning Xia8db632f2016-08-19 11:01:35 -07001001 self.assertFalse(gs_offloader._is_valid_result(
1002 None, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1003 self.assertFalse(gs_offloader._is_valid_result(
1004 release_build, gs_offloader.CTS_RESULT_PATTERN, None))
1005 self.assertFalse(gs_offloader._is_valid_result(
1006 pfq_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1007 self.assertFalse(gs_offloader._is_valid_result(
1008 trybot_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1009 self.assertFalse(gs_offloader._is_valid_result(
1010 trybot_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1011 self.assertTrue(gs_offloader._is_valid_result(
1012 release_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Ningning Xia21922c82016-07-29 11:03:15 -07001013
1014
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001015 def create_results_folder(self):
1016 """Create CTS/GTS results folders."""
Ningning Xia42111242016-06-15 14:35:58 -07001017 results_folder = tempfile.mkdtemp()
1018 host_folder = os.path.join(results_folder, 'chromeos4-row9-rack11-host22')
1019 debug_folder = os.path.join(host_folder, 'debug')
1020 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
1021 cts_result_folder = os.path.join(
1022 host_folder, 'cheets_CTS.android.dpi', 'results', 'cts-results')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001023 cts_v2_result_folder = os.path.join(host_folder,
1024 'cheets_CTS_N.CtsGraphicsTestCases', 'results', 'android-cts')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001025 gts_result_folder = os.path.join(
Ilja H. Friedelbfa63142017-01-26 00:56:29 -08001026 host_folder, 'cheets_GTS.google.admin', 'results', 'android-gts')
Ningning Xia42111242016-06-15 14:35:58 -07001027 timestamp_str = '2016.04.28_01.41.44'
Ningning Xia2d88eec2016-07-25 23:18:46 -07001028 timestamp_cts_folder = os.path.join(cts_result_folder, timestamp_str)
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001029 timestamp_cts_v2_folder = os.path.join(cts_v2_result_folder, timestamp_str)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001030 timestamp_gts_folder = os.path.join(gts_result_folder, timestamp_str)
1031
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001032 # Test results in cts_result_folder with a different time-stamp.
1033 timestamp_str_2 = '2016.04.28_10.41.44'
1034 timestamp_cts_folder_2 = os.path.join(cts_result_folder, timestamp_str_2)
1035
Ningning Xia2d88eec2016-07-25 23:18:46 -07001036 for folder in [debug_folder, sysinfo_folder, cts_result_folder,
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001037 timestamp_cts_folder, timestamp_cts_folder_2,
1038 timestamp_cts_v2_folder, timestamp_gts_folder]:
Ningning Xia42111242016-06-15 14:35:58 -07001039 os.makedirs(folder)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001040
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001041 path_pattern_pair = [(timestamp_cts_folder, gs_offloader.CTS_RESULT_PATTERN),
1042 (timestamp_cts_folder_2, gs_offloader.CTS_RESULT_PATTERN),
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001043 (timestamp_cts_v2_folder, gs_offloader.CTS_V2_RESULT_PATTERN),
1044 (timestamp_gts_folder, gs_offloader.CTS_V2_RESULT_PATTERN)]
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001045
1046 # Create timestamp.zip file_path.
Ningning Xia2d88eec2016-07-25 23:18:46 -07001047 cts_zip_file = os.path.join(cts_result_folder, timestamp_str + '.zip')
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001048 cts_zip_file_2 = os.path.join(cts_result_folder, timestamp_str_2 + '.zip')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001049 cts_v2_zip_file = os.path.join(cts_v2_result_folder, timestamp_str + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001050 gts_zip_file = os.path.join(gts_result_folder, timestamp_str + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001051
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001052 # Create xml file_path.
1053 cts_result_file = os.path.join(timestamp_cts_folder, 'testResult.xml')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001054 cts_result_file_2 = os.path.join(timestamp_cts_folder_2,
1055 'testResult.xml')
Ilja H. Friedelbfa63142017-01-26 00:56:29 -08001056 gts_result_file = os.path.join(timestamp_gts_folder, 'test_result.xml')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001057 cts_v2_result_file = os.path.join(timestamp_cts_v2_folder,
1058 'test_result.xml')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001059
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001060 for file_path in [cts_zip_file, cts_zip_file_2, cts_v2_zip_file,
1061 gts_zip_file, cts_result_file, cts_result_file_2,
1062 gts_result_file, cts_v2_result_file]:
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001063 with open(file_path, 'w') as f:
1064 f.write('test')
Ningning Xia42111242016-06-15 14:35:58 -07001065
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001066 return (results_folder, host_folder, path_pattern_pair)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001067
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001068
1069 def test_upload_testresult_files(self):
1070 """Test upload_testresult_files."""
1071 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
1072
1073 self.mox.StubOutWithMock(gs_offloader, '_upload_files')
1074 gs_offloader._upload_files(
1075 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1076 ['test', '-d', host_folder])
1077 gs_offloader._upload_files(
1078 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1079 ['test', '-d', host_folder])
1080 gs_offloader._upload_files(
1081 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1082 ['test', '-d', host_folder])
Ningning Xia2d88eec2016-07-25 23:18:46 -07001083
Ningning Xia42111242016-06-15 14:35:58 -07001084 self.mox.ReplayAll()
1085 gs_offloader.upload_testresult_files(results_folder, False)
1086 self.mox.VerifyAll()
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001087 shutil.rmtree(results_folder)
1088
1089
1090 def test_upload_files(self):
1091 """Test upload_files"""
1092 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
1093
1094 for path, pattern in path_pattern_pair:
1095 models.test.parse_job_keyval(mox.IgnoreArg()).AndReturn({
1096 'build': 'veyron_minnie-cheets-release/R52-8248.0.0',
Ningning Xia8db632f2016-08-19 11:01:35 -07001097 'parent_job_id': 'p_id',
1098 'suite': 'arc-cts'
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001099 })
1100
1101 gs_offloader.get_cmd_list(
1102 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
1103 ['test', '-d', path])
1104 gs_offloader.get_cmd_list(
1105 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
1106 ['test', '-d', path])
1107
1108 self.mox.ReplayAll()
1109 gs_offloader._upload_files(host_folder, path, pattern, False)
1110 self.mox.VerifyAll()
1111 self.mox.ResetAll()
Ningning Xia42111242016-06-15 14:35:58 -07001112
1113 shutil.rmtree(results_folder)
Dan Shi1b4c7c32015-10-05 10:38:57 -07001114
Michael Tang97d188c2016-06-25 11:18:42 -07001115
J. Richard Barnetteea785362014-03-17 16:00:53 -07001116class JobDirectoryOffloadTests(_TempResultsDirTestBase):
1117 """Tests for `_JobDirectory.enqueue_offload()`.
1118
1119 When testing with a `days_old` parameter of 0, we use
1120 `set_finished()` instead of `set_expired()`. This causes the
1121 job's timestamp to be set in the future. This is done so as
1122 to test that when `days_old` is 0, the job is always treated
1123 as eligible for offload, regardless of the timestamp's value.
1124
1125 Testing covers the following assertions:
1126 A. Each time `enqueue_offload()` is called, a message that
1127 includes the job's directory name will be logged using
1128 `logging.debug()`, regardless of whether the job was
1129 enqueued. Nothing else is allowed to be logged.
1130 B. If the job is not eligible to be offloaded,
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001131 `get_failure_time()` and `get_failure_count()` are 0.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001132 C. If the job is not eligible for offload, nothing is
1133 enqueued in `queue`.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001134 D. When the job is offloaded, `get_failure_count()` increments
J. Richard Barnetteea785362014-03-17 16:00:53 -07001135 each time.
1136 E. When the job is offloaded, the appropriate parameters are
1137 enqueued exactly once.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001138 F. The first time a job is offloaded, `get_failure_time()` is
J. Richard Barnetteea785362014-03-17 16:00:53 -07001139 set to the current time.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001140 G. `get_failure_time()` only changes the first time that the
J. Richard Barnetteea785362014-03-17 16:00:53 -07001141 job is offloaded.
1142
1143 The test cases below are designed to exercise all of the
1144 meaningful state transitions at least once.
1145
1146 """
1147
1148 def setUp(self):
1149 super(JobDirectoryOffloadTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001150 self._job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001151 self._queue = Queue.Queue()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001152
Jakob Juelich24f22c22014-09-26 11:46:11 -07001153
J. Richard Barnetteea785362014-03-17 16:00:53 -07001154 def _offload_unexpired_job(self, days_old):
1155 """Make calls to `enqueue_offload()` for an unexpired job.
1156
1157 This method tests assertions B and C that calling
1158 `enqueue_offload()` has no effect.
1159
1160 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001161 self.assertEqual(self._job.get_failure_count(), 0)
1162 self.assertEqual(self._job.get_failure_time(), 0)
1163 self._job.enqueue_offload(self._queue, days_old)
1164 self._job.enqueue_offload(self._queue, days_old)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001165 self.assertTrue(self._queue.empty())
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001166 self.assertEqual(self._job.get_failure_count(), 0)
1167 self.assertEqual(self._job.get_failure_time(), 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001168
Jakob Juelich24f22c22014-09-26 11:46:11 -07001169
J. Richard Barnetteea785362014-03-17 16:00:53 -07001170 def _offload_expired_once(self, days_old, count):
1171 """Make one call to `enqueue_offload()` for an expired job.
1172
1173 This method tests assertions D and E regarding side-effects
1174 expected when a job is offloaded.
1175
1176 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001177 self._job.enqueue_offload(self._queue, days_old)
1178 self.assertEqual(self._job.get_failure_count(), count)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001179 self.assertFalse(self._queue.empty())
1180 v = self._queue.get_nowait()
1181 self.assertTrue(self._queue.empty())
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -07001182 self.assertEqual(v, self._job.queue_args)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001183
Jakob Juelich24f22c22014-09-26 11:46:11 -07001184
J. Richard Barnetteea785362014-03-17 16:00:53 -07001185 def _offload_expired_job(self, days_old):
1186 """Make calls to `enqueue_offload()` for a just-expired job.
1187
1188 This method directly tests assertions F and G regarding
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001189 side-effects on `get_failure_time()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001190
1191 """
1192 t0 = time.time()
1193 self._offload_expired_once(days_old, 1)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001194 t1 = self._job.get_failure_time()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001195 self.assertLessEqual(t1, time.time())
1196 self.assertGreaterEqual(t1, t0)
1197 self._offload_expired_once(days_old, 2)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001198 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001199 self._offload_expired_once(days_old, 3)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001200 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001201
Jakob Juelich24f22c22014-09-26 11:46:11 -07001202
J. Richard Barnetteea785362014-03-17 16:00:53 -07001203 def test_case_1_no_expiration(self):
1204 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1205
1206 This tests that offload works as expected if calls are
1207 made both before and after the job becomes expired.
1208
1209 """
1210 self._offload_unexpired_job(0)
1211 self._job.set_finished(0)
1212 self._offload_expired_job(0)
1213
Jakob Juelich24f22c22014-09-26 11:46:11 -07001214
J. Richard Barnetteea785362014-03-17 16:00:53 -07001215 def test_case_2_no_expiration(self):
1216 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1217
1218 This tests that offload works as expected if calls are made
1219 only after the job becomes expired.
1220
1221 """
1222 self._job.set_finished(0)
1223 self._offload_expired_job(0)
1224
Jakob Juelich24f22c22014-09-26 11:46:11 -07001225
J. Richard Barnetteea785362014-03-17 16:00:53 -07001226 def test_case_1_with_expiration(self):
1227 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1228
1229 This tests that offload works as expected if calls are made
1230 before the job finishes, before the job expires, and after
1231 the job expires.
1232
1233 """
1234 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1235 self._job.set_finished(_TEST_EXPIRATION_AGE)
1236 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1237 self._job.set_expired(_TEST_EXPIRATION_AGE)
1238 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1239
Jakob Juelich24f22c22014-09-26 11:46:11 -07001240
J. Richard Barnetteea785362014-03-17 16:00:53 -07001241 def test_case_2_with_expiration(self):
1242 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1243
1244 This tests that offload works as expected if calls are made
1245 between finishing and expiration, and after the job expires.
1246
1247 """
1248 self._job.set_finished(_TEST_EXPIRATION_AGE)
1249 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1250 self._job.set_expired(_TEST_EXPIRATION_AGE)
1251 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1252
Jakob Juelich24f22c22014-09-26 11:46:11 -07001253
J. Richard Barnetteea785362014-03-17 16:00:53 -07001254 def test_case_3_with_expiration(self):
1255 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1256
1257 This tests that offload works as expected if calls are made
1258 only before finishing and after expiration.
1259
1260 """
1261 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1262 self._job.set_expired(_TEST_EXPIRATION_AGE)
1263 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1264
Jakob Juelich24f22c22014-09-26 11:46:11 -07001265
J. Richard Barnetteea785362014-03-17 16:00:53 -07001266 def test_case_4_with_expiration(self):
1267 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1268
1269 This tests that offload works as expected if calls are made
1270 only after expiration.
1271
1272 """
1273 self._job.set_expired(_TEST_EXPIRATION_AGE)
1274 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1275
1276
1277class GetJobDirectoriesTests(_TempResultsDirTestBase):
1278 """Tests for `_JobDirectory.get_job_directories()`."""
1279
J. Richard Barnetteea785362014-03-17 16:00:53 -07001280 def setUp(self):
1281 super(GetJobDirectoriesTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001282 self.make_job_hierarchy()
1283 os.mkdir('not-a-job')
1284 open('not-a-dir', 'w').close()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001285
Jakob Juelich24f22c22014-09-26 11:46:11 -07001286
J. Richard Barnetteea785362014-03-17 16:00:53 -07001287 def _run_get_directories(self, cls, expected_list):
1288 """Test `get_job_directories()` for the given class.
1289
1290 Calls the method, and asserts that the returned list of
1291 directories matches the expected return value.
1292
1293 @param expected_list Expected return value from the call.
1294 """
J. Richard Barnetteea785362014-03-17 16:00:53 -07001295 dirlist = cls.get_job_directories()
1296 self.assertEqual(set(dirlist), set(expected_list))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001297
Jakob Juelich24f22c22014-09-26 11:46:11 -07001298
J. Richard Barnetteea785362014-03-17 16:00:53 -07001299 def test_get_regular_jobs(self):
1300 """Test `RegularJobDirectory.get_job_directories()`."""
1301 self._run_get_directories(job_directories.RegularJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001302 self.REGULAR_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001303
Jakob Juelich24f22c22014-09-26 11:46:11 -07001304
J. Richard Barnetteea785362014-03-17 16:00:53 -07001305 def test_get_special_jobs(self):
1306 """Test `SpecialJobDirectory.get_job_directories()`."""
1307 self._run_get_directories(job_directories.SpecialJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001308 self.SPECIAL_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001309
1310
1311class AddJobsTests(_TempResultsDirTestBase):
1312 """Tests for `Offloader._add_new_jobs()`."""
1313
J. Richard Barnette08800322014-05-16 14:49:46 -07001314 MOREJOBS = ['115-fubar', '116-fubar', '117-fubar', '118-snafu']
J. Richard Barnetteea785362014-03-17 16:00:53 -07001315
1316 def setUp(self):
1317 super(AddJobsTests, self).setUp()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001318 self._initial_job_names = (
1319 set(self.REGULAR_JOBLIST) | set(self.SPECIAL_JOBLIST))
J. Richard Barnette08800322014-05-16 14:49:46 -07001320 self.make_job_hierarchy()
1321 self._offloader = gs_offloader.Offloader(_get_options(['-a']))
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001322 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001323
Jakob Juelich24f22c22014-09-26 11:46:11 -07001324
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001325 def _run_add_new_jobs(self, expected_key_set):
J. Richard Barnetteea785362014-03-17 16:00:53 -07001326 """Basic test assertions for `_add_new_jobs()`.
1327
1328 Asserts the following:
1329 * The keys in the offloader's `_open_jobs` dictionary
1330 matches the expected set of keys.
1331 * For every job in `_open_jobs`, the job has the expected
1332 directory name.
1333
1334 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001335 count = len(expected_key_set) - len(self._offloader._open_jobs)
1336 logging.debug(mox.IgnoreArg(), count)
1337 self.mox.ReplayAll()
1338 self._offloader._add_new_jobs()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001339 self.assertEqual(expected_key_set,
1340 set(self._offloader._open_jobs.keys()))
1341 for jobkey, job in self._offloader._open_jobs.items():
1342 self.assertEqual(jobkey, job._dirname)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001343 self.mox.VerifyAll()
1344 self.mox.ResetAll()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001345
Jakob Juelich24f22c22014-09-26 11:46:11 -07001346
J. Richard Barnetteea785362014-03-17 16:00:53 -07001347 def test_add_jobs_empty(self):
1348 """Test adding jobs to an empty dictionary.
1349
1350 Calls the offloader's `_add_new_jobs()`, then perform
1351 the assertions of `self._check_open_jobs()`.
1352
1353 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001354 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001355
Jakob Juelich24f22c22014-09-26 11:46:11 -07001356
J. Richard Barnetteea785362014-03-17 16:00:53 -07001357 def test_add_jobs_non_empty(self):
1358 """Test adding jobs to a non-empty dictionary.
1359
1360 Calls the offloader's `_add_new_jobs()` twice; once from
1361 initial conditions, and then again after adding more
1362 directories. After the second call, perform the assertions
1363 of `self._check_open_jobs()`. Additionally, assert that
1364 keys added by the first call still map to their original
1365 job object after the second call.
1366
1367 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001368 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001369 jobs_copy = self._offloader._open_jobs.copy()
J. Richard Barnette08800322014-05-16 14:49:46 -07001370 for d in self.MOREJOBS:
1371 os.mkdir(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001372 self._run_add_new_jobs(self._initial_job_names |
1373 set(self.MOREJOBS))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001374 for key in jobs_copy.keys():
1375 self.assertIs(jobs_copy[key],
1376 self._offloader._open_jobs[key])
1377
1378
1379class JobStateTests(_TempResultsDirTestBase):
1380 """Tests for job state predicates.
1381
1382 This tests for the expected results from the
Prathmesh Prabhuca481592017-01-30 18:05:49 -08001383 `is_offloaded()` predicate method.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001384
1385 """
1386
1387 def test_unfinished_job(self):
1388 """Test that an unfinished job reports the correct state.
1389
1390 A job is "unfinished" if it isn't marked complete in the
1391 database. A job in this state is neither "complete" nor
1392 "reportable".
1393
1394 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001395 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001396 self.assertFalse(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001397
Jakob Juelich24f22c22014-09-26 11:46:11 -07001398
J. Richard Barnetteea785362014-03-17 16:00:53 -07001399 def test_incomplete_job(self):
1400 """Test that an incomplete job reports the correct state.
1401
1402 A job is "incomplete" if exactly one attempt has been made
1403 to offload the job, but its results directory still exists.
1404 A job in this state is neither "complete" nor "reportable".
1405
1406 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001407 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001408 job.set_incomplete()
1409 self.assertFalse(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001410
Jakob Juelich24f22c22014-09-26 11:46:11 -07001411
J. Richard Barnetteea785362014-03-17 16:00:53 -07001412 def test_reportable_job(self):
1413 """Test that a reportable job reports the correct state.
1414
1415 A job is "reportable" if more than one attempt has been made
1416 to offload the job, and its results directory still exists.
1417 A job in this state is "reportable", but not "complete".
1418
1419 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001420 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001421 job.set_reportable()
1422 self.assertFalse(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001423
Jakob Juelich24f22c22014-09-26 11:46:11 -07001424
J. Richard Barnetteea785362014-03-17 16:00:53 -07001425 def test_completed_job(self):
1426 """Test that a completed job reports the correct state.
1427
1428 A job is "completed" if at least one attempt has been made
1429 to offload the job, and its results directory still exists.
1430 A job in this state is "complete", and not "reportable".
1431
1432 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001433 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001434 job.set_complete()
1435 self.assertTrue(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001436
1437
1438class ReportingTests(_TempResultsDirTestBase):
1439 """Tests for `Offloader._update_offload_results()`."""
1440
J. Richard Barnetteea785362014-03-17 16:00:53 -07001441 def setUp(self):
1442 super(ReportingTests, self).setUp()
1443 self._offloader = gs_offloader.Offloader(_get_options([]))
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001444 self.mox.StubOutWithMock(self._offloader, '_log_failed_jobs_locally')
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001445 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001446
Jakob Juelich24f22c22014-09-26 11:46:11 -07001447
J. Richard Barnetteea785362014-03-17 16:00:53 -07001448 def _add_job(self, jobdir):
1449 """Add a job to the dictionary of unfinished jobs."""
1450 j = self.make_job(jobdir)
1451 self._offloader._open_jobs[j._dirname] = j
1452 return j
1453
Jakob Juelich24f22c22014-09-26 11:46:11 -07001454
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001455 def _expect_log_message(self, new_open_jobs, with_failures):
1456 """Mock expected logging calls.
1457
1458 `_update_offload_results()` logs one message with the number
1459 of jobs removed from the open job set and the number of jobs
1460 still remaining. Additionally, if there are reportable
1461 jobs, then it logs the number of jobs that haven't yet
1462 offloaded.
1463
1464 This sets up the logging calls using `new_open_jobs` to
1465 figure the job counts. If `with_failures` is true, then
1466 the log message is set up assuming that all jobs in
1467 `new_open_jobs` have offload failures.
1468
1469 @param new_open_jobs New job set for calculating counts
1470 in the messages.
1471 @param with_failures Whether the log message with a
1472 failure count is expected.
1473
1474 """
1475 count = len(self._offloader._open_jobs) - len(new_open_jobs)
1476 logging.debug(mox.IgnoreArg(), count, len(new_open_jobs))
1477 if with_failures:
1478 logging.debug(mox.IgnoreArg(), len(new_open_jobs))
1479
Jakob Juelich24f22c22014-09-26 11:46:11 -07001480
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001481 def _run_update(self, new_open_jobs):
1482 """Call `_update_offload_results()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001483
1484 Initial conditions are set up by the caller. This calls
1485 `_update_offload_results()` once, and then checks these
1486 assertions:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001487 * The offloader's new `_open_jobs` field contains only
1488 the entries in `new_open_jobs`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001489
1490 @param new_open_jobs A dictionary representing the expected
1491 new value of the offloader's
1492 `_open_jobs` field.
1493 """
1494 self.mox.ReplayAll()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001495 self._offloader._update_offload_results()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001496 self.assertEqual(self._offloader._open_jobs, new_open_jobs)
1497 self.mox.VerifyAll()
1498 self.mox.ResetAll()
1499
Jakob Juelich24f22c22014-09-26 11:46:11 -07001500
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001501 def _expect_failed_jobs(self, failed_jobs):
1502 """Mock expected call to log the failed jobs on local disk.
1503
1504 TODO(crbug.com/686904): The fact that we have to mock an internal
1505 function for this test is evidence that we need to pull out the local
1506 file formatter in its own object in a future CL.
1507
1508 @param failed_jobs: The list of jobs being logged as failed.
1509 """
1510 self._offloader._log_failed_jobs_locally(failed_jobs)
1511
1512
J. Richard Barnetteea785362014-03-17 16:00:53 -07001513 def test_no_jobs(self):
1514 """Test `_update_offload_results()` with no open jobs.
1515
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001516 Initial conditions are an empty `_open_jobs` list.
1517 Expected result is an empty `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001518
1519 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001520 self._expect_log_message({}, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001521 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001522 self._run_update({})
J. Richard Barnetteea785362014-03-17 16:00:53 -07001523
Jakob Juelich24f22c22014-09-26 11:46:11 -07001524
J. Richard Barnetteea785362014-03-17 16:00:53 -07001525 def test_all_completed(self):
1526 """Test `_update_offload_results()` with only complete jobs.
1527
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001528 Initial conditions are an `_open_jobs` list consisting of only completed
1529 jobs.
1530 Expected result is an empty `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001531
1532 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001533 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001534 self._add_job(d).set_complete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001535 self._expect_log_message({}, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001536 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001537 self._run_update({})
J. Richard Barnetteea785362014-03-17 16:00:53 -07001538
Jakob Juelich24f22c22014-09-26 11:46:11 -07001539
J. Richard Barnetteea785362014-03-17 16:00:53 -07001540 def test_none_finished(self):
1541 """Test `_update_offload_results()` with only unfinished jobs.
1542
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001543 Initial conditions are an `_open_jobs` list consisting of only
1544 unfinished jobs.
1545 Expected result is no change to the `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001546
1547 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001548 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001549 self._add_job(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001550 new_jobs = self._offloader._open_jobs.copy()
1551 self._expect_log_message(new_jobs, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001552 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001553 self._run_update(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001554
1555
1556if __name__ == '__main__':
1557 unittest.main()