blob: 7aa65fc6fd700a288ddcc4ede60c308ee9a9bfc2 [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
J. Richard Barnetteea785362014-03-17 16:00:53 -070024from autotest_lib.scheduler import email_manager
Prathmesh Prabhubeb9e012017-01-30 16:18:39 -080025from autotest_lib.site_utils import gs_offloader
26from autotest_lib.site_utils import job_directories
Ningning Xia2d981ee2016-07-06 17:59:54 -070027from autotest_lib.tko import models
Jakob Juelich24f22c22014-09-26 11:46:11 -070028
J. Richard Barnetteea785362014-03-17 16:00:53 -070029# Test value to use for `days_old`, if nothing else is required.
30_TEST_EXPIRATION_AGE = 7
31
32# When constructing sample time values for testing expiration,
33# allow this many seconds between the expiration time and the
34# current time.
35_MARGIN_SECS = 10.0
36
37
38def _get_options(argv):
39 """Helper function to exercise command line parsing.
40
41 @param argv Value of sys.argv to be parsed.
42
43 """
44 sys.argv = ['bogus.py'] + argv
45 return gs_offloader.parse_options()
46
47
Simran Basidd129972014-09-11 14:34:49 -070048class OffloaderOptionsTests(mox.MoxTestBase):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070049 """Tests for the `Offloader` constructor.
50
51 Tests that offloader instance fields are set as expected
52 for given command line options.
53
54 """
55
56 _REGULAR_ONLY = set([job_directories.RegularJobDirectory])
57 _SPECIAL_ONLY = set([job_directories.SpecialJobDirectory])
58 _BOTH = _REGULAR_ONLY | _SPECIAL_ONLY
J. Richard Barnetteea785362014-03-17 16:00:53 -070059
Jakob Juelich24f22c22014-09-26 11:46:11 -070060
Simran Basidd129972014-09-11 14:34:49 -070061 def setUp(self):
62 super(OffloaderOptionsTests, self).setUp()
63 self.mox.StubOutWithMock(utils, 'get_offload_gsuri')
Simran Basif3e305f2014-10-03 14:43:53 -070064 gs_offloader.GS_OFFLOADING_ENABLED = True
Michael Tang0df2eb42016-05-13 19:06:54 -070065 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
Simran Basidd129972014-09-11 14:34:49 -070066
Jakob Juelich24f22c22014-09-26 11:46:11 -070067
Michael Tang97d188c2016-06-25 11:18:42 -070068 def _mock_get_offload_func(self, is_moblab, multiprocessing=False,
Keith Haddow5ba5fb82016-11-09 11:39:36 -080069 pubsub_topic=None, delete_age=0):
Simran Basidd129972014-09-11 14:34:49 -070070 """Mock the process of getting the offload_dir function."""
71 if is_moblab:
72 expected_gsuri = '%sresults/%s/%s/' % (
73 global_config.global_config.get_config_value(
74 'CROS', 'image_storage_server'),
75 'Fa:ke:ma:c0:12:34', 'rand0m-uu1d')
76 else:
77 expected_gsuri = utils.DEFAULT_OFFLOAD_GSURI
78 utils.get_offload_gsuri().AndReturn(expected_gsuri)
Keith Haddow5ba5fb82016-11-09 11:39:36 -080079 offload_func = gs_offloader.get_offload_dir_func(expected_gsuri,
80 multiprocessing, delete_age, pubsub_topic)
Simran Basidd129972014-09-11 14:34:49 -070081 self.mox.StubOutWithMock(gs_offloader, 'get_offload_dir_func')
Michael Tang97d188c2016-06-25 11:18:42 -070082 gs_offloader.get_offload_dir_func(expected_gsuri, multiprocessing,
Keith Haddow5ba5fb82016-11-09 11:39:36 -080083 delete_age, pubsub_topic).AndReturn(offload_func)
Simran Basidd129972014-09-11 14:34:49 -070084 self.mox.ReplayAll()
85 return offload_func
86
Jakob Juelich24f22c22014-09-26 11:46:11 -070087
J. Richard Barnetteea785362014-03-17 16:00:53 -070088 def test_process_no_options(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070089 """Test default offloader options."""
Simran Basidd129972014-09-11 14:34:49 -070090 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070091 offloader = gs_offloader.Offloader(_get_options([]))
92 self.assertEqual(set(offloader._jobdir_classes),
93 self._REGULAR_ONLY)
94 self.assertEqual(offloader._processes, 1)
95 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -070096 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -080097 self.assertEqual(offloader._upload_age_limit, 0)
98 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -070099
Jakob Juelich24f22c22014-09-26 11:46:11 -0700100
J. Richard Barnetteea785362014-03-17 16:00:53 -0700101 def test_process_all_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700102 """Test offloader handling for the --all option."""
Simran Basidd129972014-09-11 14:34:49 -0700103 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700104 offloader = gs_offloader.Offloader(_get_options(['--all']))
105 self.assertEqual(set(offloader._jobdir_classes), self._BOTH)
106 self.assertEqual(offloader._processes, 1)
107 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700108 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800109 self.assertEqual(offloader._upload_age_limit, 0)
110 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700111
Jakob Juelich24f22c22014-09-26 11:46:11 -0700112
J. Richard Barnetteea785362014-03-17 16:00:53 -0700113 def test_process_hosts_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700114 """Test offloader handling for the --hosts option."""
Simran Basidd129972014-09-11 14:34:49 -0700115 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700116 offloader = gs_offloader.Offloader(
117 _get_options(['--hosts']))
118 self.assertEqual(set(offloader._jobdir_classes),
119 self._SPECIAL_ONLY)
120 self.assertEqual(offloader._processes, 1)
121 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700122 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800123 self.assertEqual(offloader._upload_age_limit, 0)
124 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700125
Jakob Juelich24f22c22014-09-26 11:46:11 -0700126
J. Richard Barnetteea785362014-03-17 16:00:53 -0700127 def test_parallelism_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700128 """Test offloader handling for the --parallelism option."""
Simran Basidd129972014-09-11 14:34:49 -0700129 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700130 offloader = gs_offloader.Offloader(
131 _get_options(['--parallelism', '2']))
132 self.assertEqual(set(offloader._jobdir_classes),
133 self._REGULAR_ONLY)
134 self.assertEqual(offloader._processes, 2)
135 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700136 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800137 self.assertEqual(offloader._upload_age_limit, 0)
138 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700139
Jakob Juelich24f22c22014-09-26 11:46:11 -0700140
J. Richard Barnetteea785362014-03-17 16:00:53 -0700141 def test_delete_only_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700142 """Test offloader handling for the --delete_only option."""
143 offloader = gs_offloader.Offloader(
144 _get_options(['--delete_only']))
145 self.assertEqual(set(offloader._jobdir_classes),
146 self._REGULAR_ONLY)
147 self.assertEqual(offloader._processes, 1)
148 self.assertEqual(offloader._offload_func,
149 gs_offloader.delete_files)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800150 self.assertEqual(offloader._upload_age_limit, 0)
151 self.assertEqual(offloader._delete_age_limit, 0)
Michael Tang97d188c2016-06-25 11:18:42 -0700152 self.assertIsNone(offloader._pubsub_topic)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700153
Jakob Juelich24f22c22014-09-26 11:46:11 -0700154
Simran Basidf4751e2014-10-10 14:19:22 -0700155 def test_days_old_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700156 """Test offloader handling for the --days_old option."""
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800157 offload_func = self._mock_get_offload_func(False, delete_age=7)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700158 offloader = gs_offloader.Offloader(
159 _get_options(['--days_old', '7']))
160 self.assertEqual(set(offloader._jobdir_classes),
161 self._REGULAR_ONLY)
162 self.assertEqual(offloader._processes, 1)
163 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700164 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800165 self.assertEqual(offloader._upload_age_limit, 7)
166 self.assertEqual(offloader._delete_age_limit, 7)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700167
Jakob Juelich24f22c22014-09-26 11:46:11 -0700168
Simran Basidd129972014-09-11 14:34:49 -0700169 def test_moblab_gsuri_generation(self):
170 """Test offloader construction for Moblab."""
171 offload_func = self._mock_get_offload_func(True)
172 offloader = gs_offloader.Offloader(_get_options([]))
173 self.assertEqual(set(offloader._jobdir_classes),
174 self._REGULAR_ONLY)
175 self.assertEqual(offloader._processes, 1)
176 self.assertEqual(offloader._offload_func,
177 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800178 self.assertEqual(offloader._upload_age_limit, 0)
179 self.assertEqual(offloader._delete_age_limit, 0)
Simran Basidd129972014-09-11 14:34:49 -0700180
J. Richard Barnetteea785362014-03-17 16:00:53 -0700181
Simran Basif3e305f2014-10-03 14:43:53 -0700182 def test_globalconfig_offloading_flag(self):
183 """Test enabling of --delete_only via global_config."""
184 gs_offloader.GS_OFFLOADING_ENABLED = False
185 offloader = gs_offloader.Offloader(
186 _get_options([]))
187 self.assertEqual(offloader._offload_func,
188 gs_offloader.delete_files)
189
Michael Tang0df2eb42016-05-13 19:06:54 -0700190 def test_offloader_multiprocessing_flag_set(self):
191 """Test multiprocessing is set."""
192 offload_func = self._mock_get_offload_func(True, True)
193 offloader = gs_offloader.Offloader(_get_options(['-m']))
194 self.assertEqual(offloader._offload_func,
195 offload_func)
196 self.mox.VerifyAll()
197
198 def test_offloader_multiprocessing_flag_not_set_default_false(self):
199 """Test multiprocessing is set."""
200 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
201 offload_func = self._mock_get_offload_func(True, False)
202 offloader = gs_offloader.Offloader(_get_options([]))
203 self.assertEqual(offloader._offload_func,
204 offload_func)
205 self.mox.VerifyAll()
206
207 def test_offloader_multiprocessing_flag_not_set_default_true(self):
208 """Test multiprocessing is set."""
209 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = True
210 offload_func = self._mock_get_offload_func(True, True)
211 offloader = gs_offloader.Offloader(_get_options([]))
212 self.assertEqual(offloader._offload_func,
213 offload_func)
214 self.mox.VerifyAll()
215
Michael Tang97d188c2016-06-25 11:18:42 -0700216 def test_offloader_pubsub_topic_not_set(self):
217 """Test multiprocessing is set."""
218 offload_func = self._mock_get_offload_func(True, False)
219 offloader = gs_offloader.Offloader(_get_options([]))
220 self.assertEqual(offloader._offload_func,
221 offload_func)
222 self.mox.VerifyAll()
223
224 def test_offloader_pubsub_topic_set(self):
225 """Test multiprocessing is set."""
226 offload_func = self._mock_get_offload_func(True, False, 'test-topic')
227 offloader = gs_offloader.Offloader(_get_options(['-t', 'test-topic']))
228 self.assertEqual(offloader._offload_func,
229 offload_func)
230 self.mox.VerifyAll()
231
Simran Basif3e305f2014-10-03 14:43:53 -0700232
J. Richard Barnetteea785362014-03-17 16:00:53 -0700233def _make_timestamp(age_limit, is_expired):
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800234 """Create a timestamp for use by `job_directories.is_job_expired()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -0700235
236 The timestamp will meet the syntactic requirements for
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800237 timestamps used as input to `is_job_expired()`. If
J. Richard Barnetteea785362014-03-17 16:00:53 -0700238 `is_expired` is true, the timestamp will be older than
239 `age_limit` days before the current time; otherwise, the
240 date will be younger.
241
242 @param age_limit The number of days before expiration of the
243 target timestamp.
244 @param is_expired Whether the timestamp should be expired
245 relative to `age_limit`.
246
247 """
248 seconds = -_MARGIN_SECS
249 if is_expired:
250 seconds = -seconds
251 delta = datetime.timedelta(days=age_limit, seconds=seconds)
252 reference_time = datetime.datetime.now() - delta
Dan Shidfea3682014-08-10 23:38:40 -0700253 return reference_time.strftime(time_utils.TIME_FMT)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700254
255
256class JobExpirationTests(unittest.TestCase):
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800257 """Tests to exercise `job_directories.is_job_expired()`."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700258
259 def test_expired(self):
260 """Test detection of an expired job."""
261 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, True)
262 self.assertTrue(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800263 job_directories.is_job_expired(
J. Richard Barnetteea785362014-03-17 16:00:53 -0700264 _TEST_EXPIRATION_AGE, timestamp))
265
266
267 def test_alive(self):
268 """Test detection of a job that's not expired."""
269 # N.B. This test may fail if its run time exceeds more than
270 # about _MARGIN_SECS seconds.
271 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, False)
272 self.assertFalse(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800273 job_directories.is_job_expired(
J. Richard Barnetteea785362014-03-17 16:00:53 -0700274 _TEST_EXPIRATION_AGE, timestamp))
275
276
277class _MockJobDirectory(job_directories._JobDirectory):
278 """Subclass of `_JobDirectory` used as a helper for tests."""
279
280 GLOB_PATTERN = '[0-9]*-*'
281
Jakob Juelich24f22c22014-09-26 11:46:11 -0700282
J. Richard Barnetteea785362014-03-17 16:00:53 -0700283 def __init__(self, resultsdir):
284 """Create new job in initial state."""
285 super(_MockJobDirectory, self).__init__(resultsdir)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700286 self._timestamp = None
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800287 self.queue_args = [resultsdir, os.path.dirname(resultsdir), self._timestamp]
J. Richard Barnetteea785362014-03-17 16:00:53 -0700288
Jakob Juelich24f22c22014-09-26 11:46:11 -0700289
J. Richard Barnetteea785362014-03-17 16:00:53 -0700290 def get_timestamp_if_finished(self):
291 return self._timestamp
292
Jakob Juelich24f22c22014-09-26 11:46:11 -0700293
J. Richard Barnetteea785362014-03-17 16:00:53 -0700294 def set_finished(self, days_old):
295 """Make this job appear to be finished.
296
297 After calling this function, calls to `enqueue_offload()`
298 will find this job as finished, but not expired and ready
299 for offload. Note that when `days_old` is 0,
300 `enqueue_offload()` will treat a finished job as eligible
301 for offload.
302
303 @param days_old The value of the `days_old` parameter that
304 will be passed to `enqueue_offload()` for
305 testing.
306
307 """
308 self._timestamp = _make_timestamp(days_old, False)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800309 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700310
Jakob Juelich24f22c22014-09-26 11:46:11 -0700311
J. Richard Barnetteea785362014-03-17 16:00:53 -0700312 def set_expired(self, days_old):
313 """Make this job eligible to be offloaded.
314
315 After calling this function, calls to `offload` will attempt
316 to offload this job.
317
318 @param days_old The value of the `days_old` parameter that
319 will be passed to `enqueue_offload()` for
320 testing.
321
322 """
323 self._timestamp = _make_timestamp(days_old, True)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800324 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700325
Jakob Juelich24f22c22014-09-26 11:46:11 -0700326
J. Richard Barnetteea785362014-03-17 16:00:53 -0700327 def set_incomplete(self):
328 """Make this job appear to have failed offload just once."""
329 self._offload_count += 1
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700330 self._first_offload_start = time.time()
J. Richard Barnetteea785362014-03-17 16:00:53 -0700331 if not os.path.isdir(self._dirname):
332 os.mkdir(self._dirname)
333
Jakob Juelich24f22c22014-09-26 11:46:11 -0700334
J. Richard Barnetteea785362014-03-17 16:00:53 -0700335 def set_reportable(self):
336 """Make this job be reportable."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700337 self.set_incomplete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700338 self._offload_count += 1
J. Richard Barnetteea785362014-03-17 16:00:53 -0700339
Jakob Juelich24f22c22014-09-26 11:46:11 -0700340
J. Richard Barnetteea785362014-03-17 16:00:53 -0700341 def set_complete(self):
342 """Make this job be completed."""
343 self._offload_count += 1
344 if os.path.isdir(self._dirname):
345 os.rmdir(self._dirname)
346
347
Simran Basi1e10e922015-04-16 15:09:56 -0700348 def process_gs_instructions(self):
349 """Always still offload the job directory."""
350 return True
351
352
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700353class CommandListTests(unittest.TestCase):
354 """Tests for `get_cmd_list()`."""
355
MK Ryue93c8572015-08-11 11:53:00 -0700356 def _command_list_assertions(self, job, use_rsync=True, multi=False):
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700357 """Call `get_cmd_list()` and check the return value.
358
359 Check the following assertions:
360 * The command name (argv[0]) is 'gsutil'.
MK Ryue93c8572015-08-11 11:53:00 -0700361 * '-m' option (argv[1]) is on when the argument, multi, is True.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700362 * The arguments contain the 'cp' subcommand.
363 * The next-to-last argument (the source directory) is the
364 job's `queue_args[0]`.
Simran Basidd129972014-09-11 14:34:49 -0700365 * The last argument (the destination URL) is the job's
366 'queue_args[1]'.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700367
368 @param job A job with properly calculated arguments to
369 `get_cmd_list()`
MK Ryue93c8572015-08-11 11:53:00 -0700370 @param use_rsync True when using 'rsync'. False when using 'cp'.
371 @param multi True when using '-m' option for gsutil.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700372
373 """
Jakob Juelich24f22c22014-09-26 11:46:11 -0700374 test_bucket_uri = 'gs://a-test-bucket'
375
376 gs_offloader.USE_RSYNC_ENABLED = use_rsync
377
378 command = gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700379 multi, job.queue_args[0],
380 os.path.join(test_bucket_uri, job.queue_args[1]))
Jakob Juelich24f22c22014-09-26 11:46:11 -0700381
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700382 self.assertEqual(command[0], 'gsutil')
MK Ryue93c8572015-08-11 11:53:00 -0700383 if multi:
384 self.assertEqual(command[1], '-m')
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700385 self.assertEqual(command[-2], job.queue_args[0])
Jakob Juelich24f22c22014-09-26 11:46:11 -0700386
387 if use_rsync:
388 self.assertTrue('rsync' in command)
389 self.assertEqual(command[-1],
390 os.path.join(test_bucket_uri, job.queue_args[0]))
391 else:
392 self.assertTrue('cp' in command)
393 self.assertEqual(command[-1],
394 os.path.join(test_bucket_uri, job.queue_args[1]))
395
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700396
397 def test_get_cmd_list_regular(self):
398 """Test `get_cmd_list()` as for a regular job."""
399 job = _MockJobDirectory('118-debug')
400 self._command_list_assertions(job)
401
Jakob Juelich24f22c22014-09-26 11:46:11 -0700402
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700403 def test_get_cmd_list_special(self):
404 """Test `get_cmd_list()` as for a special job."""
405 job = _MockJobDirectory('hosts/host1/118-reset')
406 self._command_list_assertions(job)
407
408
Jakob Juelich24f22c22014-09-26 11:46:11 -0700409 def test_get_cmd_list_regular_no_rsync(self):
410 """Test `get_cmd_list()` as for a regular job."""
411 job = _MockJobDirectory('118-debug')
412 self._command_list_assertions(job, use_rsync=False)
413
414
415 def test_get_cmd_list_special_no_rsync(self):
416 """Test `get_cmd_list()` as for a special job."""
417 job = _MockJobDirectory('hosts/host1/118-reset')
418 self._command_list_assertions(job, use_rsync=False)
419
420
MK Ryue93c8572015-08-11 11:53:00 -0700421 def test_get_cmd_list_regular_multi(self):
422 """Test `get_cmd_list()` as for a regular job with True multi."""
423 job = _MockJobDirectory('118-debug')
424 self._command_list_assertions(job, multi=True)
425
426
427 def test_get_cmd_list_special_multi(self):
428 """Test `get_cmd_list()` as for a special job with True multi."""
429 job = _MockJobDirectory('hosts/host1/118-reset')
430 self._command_list_assertions(job, multi=True)
431
432
J. Richard Barnetteea785362014-03-17 16:00:53 -0700433# Below is partial sample of e-mail notification text. This text is
434# deliberately hard-coded and then parsed to create the test data;
435# the idea is to make sure the actual text format will be reviewed
436# by a human being.
437#
438# first offload count directory
439# --+----1----+---- ----+ ----+----1----+----2----+----3
440_SAMPLE_DIRECTORIES_REPORT = '''\
441=================== ====== ==============================
4422014-03-14 15:09:26 1 118-fubar
4432014-03-14 15:19:23 2 117-fubar
4442014-03-14 15:29:20 6 116-fubar
4452014-03-14 15:39:17 24 115-fubar
4462014-03-14 15:49:14 120 114-fubar
4472014-03-14 15:59:11 720 113-fubar
4482014-03-14 16:09:08 5040 112-fubar
4492014-03-14 16:19:05 40320 111-fubar
450'''
451
452
453class EmailTemplateTests(mox.MoxTestBase):
454 """Test the formatting of e-mail notifications."""
455
456 def setUp(self):
457 super(EmailTemplateTests, self).setUp()
458 self.mox.StubOutWithMock(email_manager.manager,
459 'send_email')
460 self._joblist = []
461 for line in _SAMPLE_DIRECTORIES_REPORT.split('\n')[1 : -1]:
462 date_, time_, count, dir_ = line.split()
463 job = _MockJobDirectory(dir_)
464 job._offload_count = int(count)
Dan Shidfea3682014-08-10 23:38:40 -0700465 timestruct = time.strptime("%s %s" % (date_, time_),
466 gs_offloader.ERROR_EMAIL_TIME_FORMAT)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700467 job._first_offload_start = time.mktime(timestruct)
468 # enter the jobs in reverse order, to make sure we
469 # test that the output will be sorted.
470 self._joblist.insert(0, job)
471
Jakob Juelich24f22c22014-09-26 11:46:11 -0700472
J. Richard Barnetteea785362014-03-17 16:00:53 -0700473 def test_email_template(self):
474 """Trigger an e-mail report and check its contents."""
475 # The last line of the report is a separator that we
476 # repeat in the first line of our expected result data.
477 # So, we remove that separator from the end of the of
478 # the e-mail report message.
479 #
480 # The last element in the list returned by split('\n')
481 # will be an empty string, so to remove the separator,
482 # we remove the next-to-last entry in the list.
483 report_lines = gs_offloader.ERROR_EMAIL_REPORT_FORMAT.split('\n')
484 expected_message = ('\n'.join(report_lines[: -2] +
485 report_lines[-1 :]) +
486 _SAMPLE_DIRECTORIES_REPORT)
487 email_manager.manager.send_email(
488 mox.IgnoreArg(), mox.IgnoreArg(), expected_message)
489 self.mox.ReplayAll()
490 gs_offloader.report_offload_failures(self._joblist)
491
492
Kevin Cheng686ae8c2015-09-09 11:56:38 -0700493 def test_email_url(self):
494 """Check that the expected helper url is in the email header."""
495 self.assertIn(gs_offloader.ERROR_EMAIL_HELPER_URL,
496 gs_offloader.ERROR_EMAIL_REPORT_FORMAT)
497
498
Michael Tang97d188c2016-06-25 11:18:42 -0700499class PubSubTest(mox.MoxTestBase):
500 """Test the test result notifcation data structure."""
501
502 def test_create_test_result_notification(self):
503 """Tests the test result notification message."""
Michael Tang328073b2016-11-01 15:33:38 -0700504 self.mox.StubOutWithMock(site_utils, 'get_moblab_id')
505 self.mox.StubOutWithMock(site_utils,
506 'get_default_interface_mac_address')
507 site_utils.get_default_interface_mac_address().AndReturn(
508 '1c:dc:d1:11:01:e1')
509 site_utils.get_moblab_id().AndReturn(
510 'c8386d92-9ad1-11e6-80f5-111111111111')
511 self.mox.ReplayAll()
Michael Tangb45c7232016-11-14 21:40:43 -0800512 msg = gs_offloader._create_test_result_notification(
513 'gs://test_bucket', '123-moblab')
Michael Tang97d188c2016-06-25 11:18:42 -0700514 self.assertEquals(base64.b64encode(
515 gs_offloader.NEW_TEST_RESULT_MESSAGE), msg['data'])
Michael Tang328073b2016-11-01 15:33:38 -0700516 self.assertEquals(
517 gs_offloader.NOTIFICATION_VERSION,
518 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_VERSION])
519 self.assertEquals(
520 '1c:dc:d1:11:01:e1',
521 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_MOBLAB_MAC])
522 self.assertEquals(
523 'c8386d92-9ad1-11e6-80f5-111111111111',
524 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_MOBLAB_ID])
525 self.assertEquals(
Michael Tangb45c7232016-11-14 21:40:43 -0800526 'gs://test_bucket/123-moblab',
Michael Tang328073b2016-11-01 15:33:38 -0700527 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_GCS_URI])
528 self.mox.VerifyAll()
Michael Tang97d188c2016-06-25 11:18:42 -0700529
530
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700531class _MockJob(object):
532 """Class to mock the return value of `AFE.get_jobs()`."""
533 def __init__(self, created):
534 self.created_on = created
535
536
537class _MockHostQueueEntry(object):
538 """Class to mock the return value of `AFE.get_host_queue_entries()`."""
539 def __init__(self, finished):
540 self.finished_on = finished
541
542
543class _MockSpecialTask(object):
544 """Class to mock the return value of `AFE.get_special_tasks()`."""
545 def __init__(self, finished):
546 self.time_finished = finished
547
548
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700549class JobDirectorySubclassTests(mox.MoxTestBase):
550 """Test specific to RegularJobDirectory and SpecialJobDirectory.
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700551
552 This provides coverage for the implementation in both
553 RegularJobDirectory and SpecialJobDirectory.
554
555 """
556
557 def setUp(self):
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700558 super(JobDirectorySubclassTests, self).setUp()
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700559 self.mox.StubOutWithMock(job_directories._AFE, 'get_jobs')
560 self.mox.StubOutWithMock(job_directories._AFE,
561 'get_host_queue_entries')
562 self.mox.StubOutWithMock(job_directories._AFE,
563 'get_special_tasks')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700564
Jakob Juelich24f22c22014-09-26 11:46:11 -0700565
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700566 def test_regular_job_fields(self):
567 """Test the constructor for `RegularJobDirectory`.
568
569 Construct a regular job, and assert that the `_dirname`
570 and `_id` attributes are set as expected.
571
572 """
573 resultsdir = '118-fubar'
574 job = job_directories.RegularJobDirectory(resultsdir)
575 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700576 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700577
Jakob Juelich24f22c22014-09-26 11:46:11 -0700578
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700579 def test_special_job_fields(self):
580 """Test the constructor for `SpecialJobDirectory`.
581
582 Construct a special job, and assert that the `_dirname`
583 and `_id` attributes are set as expected.
584
585 """
586 destdir = 'hosts/host1'
587 resultsdir = destdir + '/118-reset'
588 job = job_directories.SpecialJobDirectory(resultsdir)
589 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700590 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700591
Jakob Juelich24f22c22014-09-26 11:46:11 -0700592
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700593 def _check_finished_job(self, jobtime, hqetimes, expected):
594 """Mock and test behavior of a finished job.
595
596 Initialize the mocks for a call to
597 `get_timestamp_if_finished()`, then simulate one call.
598 Assert that the returned timestamp matches the passed
599 in expected value.
600
601 @param jobtime Time used to construct a _MockJob object.
602 @param hqetimes List of times used to construct
603 _MockHostQueueEntry objects.
604 @param expected Expected time to be returned by
605 get_timestamp_if_finished
606
607 """
608 job = job_directories.RegularJobDirectory('118-fubar')
609 job_directories._AFE.get_jobs(
610 id=job._id, finished=True).AndReturn(
611 [_MockJob(jobtime)])
612 job_directories._AFE.get_host_queue_entries(
613 finished_on__isnull=False,
614 job_id=job._id).AndReturn(
615 [_MockHostQueueEntry(t) for t in hqetimes])
616 self.mox.ReplayAll()
617 self.assertEqual(expected, job.get_timestamp_if_finished())
618 self.mox.VerifyAll()
619
620
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700621 def test_finished_regular_job(self):
622 """Test getting the timestamp for a finished regular job.
623
624 Tests the return value for
625 `RegularJobDirectory.get_timestamp_if_finished()` when
626 the AFE indicates the job is finished.
627
628 """
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700629 created_timestamp = _make_timestamp(1, True)
630 hqe_timestamp = _make_timestamp(0, True)
631 self._check_finished_job(created_timestamp,
632 [hqe_timestamp],
633 hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700634
Jakob Juelich24f22c22014-09-26 11:46:11 -0700635
Simran Basifb98e462014-08-18 12:35:44 -0700636 def test_finished_regular_job_multiple_hqes(self):
637 """Test getting the timestamp for a regular job with multiple hqes.
638
639 Tests the return value for
640 `RegularJobDirectory.get_timestamp_if_finished()` when
641 the AFE indicates the job is finished and the job has multiple host
642 queue entries.
643
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700644 Tests that the returned timestamp is the latest timestamp in
645 the list of HQEs, regardless of the returned order.
646
Simran Basifb98e462014-08-18 12:35:44 -0700647 """
Simran Basifb98e462014-08-18 12:35:44 -0700648 created_timestamp = _make_timestamp(2, True)
649 older_hqe_timestamp = _make_timestamp(1, True)
650 newer_hqe_timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700651 hqe_list = [older_hqe_timestamp,
652 newer_hqe_timestamp]
653 self._check_finished_job(created_timestamp,
654 hqe_list,
655 newer_hqe_timestamp)
656 self.mox.ResetAll()
657 hqe_list.reverse()
658 self._check_finished_job(created_timestamp,
659 hqe_list,
660 newer_hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700661
Jakob Juelich24f22c22014-09-26 11:46:11 -0700662
Simran Basifb98e462014-08-18 12:35:44 -0700663 def test_finished_regular_job_null_finished_times(self):
664 """Test getting the timestamp for an aborted regular job.
665
666 Tests the return value for
667 `RegularJobDirectory.get_timestamp_if_finished()` when
668 the AFE indicates the job is finished and the job has aborted host
669 queue entries.
670
671 """
Simran Basifb98e462014-08-18 12:35:44 -0700672 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700673 self._check_finished_job(timestamp, [], timestamp)
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700674
Jakob Juelich24f22c22014-09-26 11:46:11 -0700675
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700676 def test_unfinished_regular_job(self):
677 """Test getting the timestamp for an unfinished regular job.
678
679 Tests the return value for
680 `RegularJobDirectory.get_timestamp_if_finished()` when
681 the AFE indicates the job is not finished.
682
683 """
684 job = job_directories.RegularJobDirectory('118-fubar')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700685 job_directories._AFE.get_jobs(
686 id=job._id, finished=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700687 self.mox.ReplayAll()
688 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700689 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700690
Jakob Juelich24f22c22014-09-26 11:46:11 -0700691
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700692 def test_finished_special_job(self):
693 """Test getting the timestamp for a finished special job.
694
695 Tests the return value for
696 `SpecialJobDirectory.get_timestamp_if_finished()` when
697 the AFE indicates the job is finished.
698
699 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700700 job = job_directories.SpecialJobDirectory(
701 'hosts/host1/118-reset')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700702 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700703 job_directories._AFE.get_special_tasks(
704 id=job._id, is_complete=True).AndReturn(
705 [_MockSpecialTask(timestamp)])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700706 self.mox.ReplayAll()
707 self.assertEqual(timestamp,
708 job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700709 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700710
Jakob Juelich24f22c22014-09-26 11:46:11 -0700711
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700712 def test_unfinished_special_job(self):
713 """Test getting the timestamp for an unfinished special job.
714
715 Tests the return value for
716 `SpecialJobDirectory.get_timestamp_if_finished()` when
717 the AFE indicates the job is not finished.
718
719 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700720 job = job_directories.SpecialJobDirectory(
721 'hosts/host1/118-reset')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700722 job_directories._AFE.get_special_tasks(
723 id=job._id, is_complete=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700724 self.mox.ReplayAll()
725 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700726 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700727
728
J. Richard Barnetteea785362014-03-17 16:00:53 -0700729class _TempResultsDirTestBase(mox.MoxTestBase):
730 """Base class for tests using a temporary results directory."""
731
J. Richard Barnette08800322014-05-16 14:49:46 -0700732 REGULAR_JOBLIST = [
733 '111-fubar', '112-fubar', '113-fubar', '114-snafu']
734 HOST_LIST = ['host1', 'host2', 'host3']
735 SPECIAL_JOBLIST = [
736 'hosts/host1/333-reset', 'hosts/host1/334-reset',
737 'hosts/host2/444-reset', 'hosts/host3/555-reset']
738
Jakob Juelich24f22c22014-09-26 11:46:11 -0700739
J. Richard Barnetteea785362014-03-17 16:00:53 -0700740 def setUp(self):
741 super(_TempResultsDirTestBase, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -0700742 self._resultsroot = tempfile.mkdtemp()
743 self._cwd = os.getcwd()
744 os.chdir(self._resultsroot)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700745
Jakob Juelich24f22c22014-09-26 11:46:11 -0700746
J. Richard Barnetteea785362014-03-17 16:00:53 -0700747 def tearDown(self):
J. Richard Barnette08800322014-05-16 14:49:46 -0700748 os.chdir(self._cwd)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700749 shutil.rmtree(self._resultsroot)
750 super(_TempResultsDirTestBase, self).tearDown()
751
Jakob Juelich24f22c22014-09-26 11:46:11 -0700752
J. Richard Barnetteea785362014-03-17 16:00:53 -0700753 def make_job(self, jobdir):
754 """Create a job with results in `self._resultsroot`.
755
756 @param jobdir Name of the subdirectory to be created in
757 `self._resultsroot`.
758
759 """
J. Richard Barnette08800322014-05-16 14:49:46 -0700760 os.mkdir(jobdir)
761 return _MockJobDirectory(jobdir)
762
Jakob Juelich24f22c22014-09-26 11:46:11 -0700763
J. Richard Barnette08800322014-05-16 14:49:46 -0700764 def make_job_hierarchy(self):
765 """Create a sample hierarchy of job directories.
766
767 `self.REGULAR_JOBLIST` is a list of directories for regular
768 jobs to be created; `self.SPECIAL_JOBLIST` is a list of
769 directories for special jobs to be created.
770
771 """
772 for d in self.REGULAR_JOBLIST:
773 os.mkdir(d)
774 hostsdir = 'hosts'
775 os.mkdir(hostsdir)
776 for host in self.HOST_LIST:
777 os.mkdir(os.path.join(hostsdir, host))
778 for d in self.SPECIAL_JOBLIST:
779 os.mkdir(d)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700780
781
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700782class OffloadDirectoryTests(_TempResultsDirTestBase):
783 """Tests for `offload_dir()`."""
784
785 def setUp(self):
786 super(OffloadDirectoryTests, self).setUp()
787 # offload_dir() logs messages; silence them.
788 self._saved_loglevel = logging.getLogger().getEffectiveLevel()
789 logging.getLogger().setLevel(logging.CRITICAL+1)
790 self._job = self.make_job(self.REGULAR_JOBLIST[0])
791 self.mox.StubOutWithMock(gs_offloader, 'get_cmd_list')
792 self.mox.StubOutWithMock(signal, 'alarm')
Ningning Xia2d981ee2016-07-06 17:59:54 -0700793 self.mox.StubOutWithMock(models.test, 'parse_job_keyval')
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700794
Jakob Juelich24f22c22014-09-26 11:46:11 -0700795
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700796 def tearDown(self):
797 logging.getLogger().setLevel(self._saved_loglevel)
798 super(OffloadDirectoryTests, self).tearDown()
799
Ningning Xia42111242016-06-15 14:35:58 -0700800 def _mock_upload_testresult_files(self):
801 self.mox.StubOutWithMock(gs_offloader, 'upload_testresult_files')
802 gs_offloader.upload_testresult_files(
803 mox.IgnoreArg(),mox.IgnoreArg()).AndReturn(None)
Jakob Juelich24f22c22014-09-26 11:46:11 -0700804
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800805 def _mock_create_marker_file(self):
806 self.mox.StubOutWithMock(__builtin__, 'open')
807 mock_marker_file = self.mox.CreateMock(file)
808 open(mox.IgnoreArg(), 'a').AndReturn(mock_marker_file)
809 mock_marker_file.close()
810
811
812 def _mock_offload_dir_calls(self, command, queue_args,
813 marker_initially_exists=False,
814 marker_eventually_exists=True):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700815 """Mock out the calls needed by `offload_dir()`.
816
817 This covers only the calls made when there is no timeout.
818
819 @param command Command list to be returned by the mocked
820 call to `get_cmd_list()`.
821
822 """
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800823 self.mox.StubOutWithMock(os.path, 'isfile')
824 os.path.isfile(mox.IgnoreArg()).AndReturn(marker_initially_exists)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700825 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
Simran Basidd129972014-09-11 14:34:49 -0700826 command.append(queue_args[0])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700827 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700828 False, queue_args[0],
829 '%s%s' % (utils.DEFAULT_OFFLOAD_GSURI,
830 queue_args[1])).AndReturn(command)
Ningning Xia42111242016-06-15 14:35:58 -0700831 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700832 signal.alarm(0)
833 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800834 os.path.isfile(mox.IgnoreArg()).AndReturn(marker_eventually_exists)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700835
Jakob Juelich24f22c22014-09-26 11:46:11 -0700836
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800837 def _run_offload_dir(self, should_succeed, delete_age):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700838 """Make one call to `offload_dir()`.
839
840 The caller ensures all mocks are set up already.
841
842 @param should_succeed True iff the call to `offload_dir()`
843 is expected to succeed and remove the
844 offloaded job directory.
845
846 """
847 self.mox.ReplayAll()
Simran Basidd129972014-09-11 14:34:49 -0700848 gs_offloader.get_offload_dir_func(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800849 utils.DEFAULT_OFFLOAD_GSURI, False, delete_age)(
MK Ryue93c8572015-08-11 11:53:00 -0700850 self._job.queue_args[0],
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800851 self._job.queue_args[1],
852 self._job.queue_args[2])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700853 self.mox.VerifyAll()
854 self.assertEqual(not should_succeed,
855 os.path.isdir(self._job.queue_args[0]))
856
Jakob Juelich24f22c22014-09-26 11:46:11 -0700857
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700858 def test_offload_success(self):
859 """Test that `offload_dir()` can succeed correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700860 self._mock_offload_dir_calls(['test', '-d'],
861 self._job.queue_args)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800862 self._mock_create_marker_file()
863 self._run_offload_dir(True, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700864
Jakob Juelich24f22c22014-09-26 11:46:11 -0700865
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700866 def test_offload_failure(self):
867 """Test that `offload_dir()` can fail correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700868 self._mock_offload_dir_calls(['test', '!', '-d'],
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800869 self._job.queue_args,
870 marker_eventually_exists=False)
871 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700872
Jakob Juelich24f22c22014-09-26 11:46:11 -0700873
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700874 def test_offload_timeout_early(self):
875 """Test that `offload_dir()` times out correctly.
876
877 This test triggers timeout at the earliest possible moment,
878 at the first call to set the timeout alarm.
879
880 """
Ningning Xia42111242016-06-15 14:35:58 -0700881 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700882 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS).AndRaise(
883 gs_offloader.TimeoutException('fubar'))
884 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800885 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700886
Jakob Juelich24f22c22014-09-26 11:46:11 -0700887
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700888 def test_offload_timeout_late(self):
889 """Test that `offload_dir()` times out correctly.
890
891 This test triggers timeout at the latest possible moment, at
892 the call to clear the timeout alarm.
893
894 """
895 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
896 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700897 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700898 ['test', '-d', self._job.queue_args[0]])
Ningning Xia42111242016-06-15 14:35:58 -0700899 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700900 signal.alarm(0).AndRaise(
901 gs_offloader.TimeoutException('fubar'))
902 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800903 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700904
905
Dan Shiaffb9222015-04-15 17:05:47 -0700906 def test_sanitize_dir(self):
907 """Test that folder/file name with invalid character can be corrected.
908 """
909 results_folder = tempfile.mkdtemp()
910 invalid_chars = '_'.join(gs_offloader.INVALID_GS_CHARS)
911 invalid_files = []
912 invalid_folder = os.path.join(
913 results_folder,
914 'invalid_name_folder_%s' % invalid_chars)
915 invalid_files.append(os.path.join(
916 invalid_folder,
917 'invalid_name_file_%s' % invalid_chars))
918 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
919 for c in range(r[0], r[1]+1):
920 # NULL cannot be in file name.
921 if c != 0:
922 invalid_files.append(os.path.join(
923 invalid_folder,
924 'invalid_name_file_%s' % chr(c)))
925 good_folder = os.path.join(results_folder, 'valid_name_folder')
926 good_file = os.path.join(good_folder, 'valid_name_file')
927 for folder in [invalid_folder, good_folder]:
928 os.makedirs(folder)
929 for f in invalid_files + [good_file]:
930 with open(f, 'w'):
931 pass
932 gs_offloader.sanitize_dir(results_folder)
933 for _, dirs, files in os.walk(results_folder):
934 for name in dirs + files:
935 self.assertEqual(name, gs_offloader.get_sanitized_name(name))
936 for c in name:
937 self.assertFalse(c in gs_offloader.INVALID_GS_CHARS)
938 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
939 self.assertFalse(ord(c) >= r[0] and ord(c) <= r[1])
940 self.assertTrue(os.path.exists(good_file))
941 shutil.rmtree(results_folder)
942
943
Dan Shi1b4c7c32015-10-05 10:38:57 -0700944 def check_limit_file_count(self, is_test_job=True):
945 """Test that folder with too many files can be compressed.
946
947 @param is_test_job: True to check the method with test job result
948 folder. Set to False for special task folder.
949 """
950 results_folder = tempfile.mkdtemp()
951 host_folder = os.path.join(
952 results_folder,
953 'lab1-host1' if is_test_job else 'hosts/lab1-host1/1-repair')
954 debug_folder = os.path.join(host_folder, 'debug')
955 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
956 for folder in [debug_folder, sysinfo_folder]:
957 os.makedirs(folder)
958 for i in range(10):
959 with open(os.path.join(folder, str(i)), 'w') as f:
960 f.write('test')
961
962 gs_offloader.MAX_FILE_COUNT = 100
963 gs_offloader.limit_file_count(
964 results_folder if is_test_job else host_folder)
965 self.assertTrue(os.path.exists(sysinfo_folder))
966
967 gs_offloader.MAX_FILE_COUNT = 10
968 gs_offloader.limit_file_count(
969 results_folder if is_test_job else host_folder)
970 self.assertFalse(os.path.exists(sysinfo_folder))
971 self.assertTrue(os.path.exists(sysinfo_folder + '.tgz'))
972 self.assertTrue(os.path.exists(debug_folder))
973
974 shutil.rmtree(results_folder)
975
976
977 def test_limit_file_count(self):
978 """Test that folder with too many files can be compressed.
979 """
980 self.check_limit_file_count(is_test_job=True)
981 self.check_limit_file_count(is_test_job=False)
982
Ningning Xia2d88eec2016-07-25 23:18:46 -0700983
Ningning Xia8db632f2016-08-19 11:01:35 -0700984 def test_is_valid_result(self):
985 """Test _is_valid_result."""
Ningning Xia21922c82016-07-29 11:03:15 -0700986 release_build = 'veyron_minnie-cheets-release/R52-8248.0.0'
987 pfq_build = 'cyan-cheets-android-pfq/R54-8623.0.0-rc1'
988 trybot_build = 'trybot-samus-release/R54-8640.0.0-b5092'
989 trybot_2_build = 'trybot-samus-pfq/R54-8640.0.0-b5092'
990 release_2_build = 'test-trybot-release/R54-8640.0.0-b5092'
Ningning Xia8db632f2016-08-19 11:01:35 -0700991 self.assertTrue(gs_offloader._is_valid_result(
992 release_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Rohit Makasana6384c102016-10-21 17:09:47 -0700993 self.assertTrue(gs_offloader._is_valid_result(
994 release_build, gs_offloader.CTS_RESULT_PATTERN, 'test_that_wrapper'))
Ningning Xia8db632f2016-08-19 11:01:35 -0700995 self.assertFalse(gs_offloader._is_valid_result(
996 release_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-bvt-cq'))
997 self.assertTrue(gs_offloader._is_valid_result(
998 release_build, gs_offloader.GTS_RESULT_PATTERN, 'arc-gts-tot'))
999 self.assertFalse(gs_offloader._is_valid_result(
1000 None, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1001 self.assertFalse(gs_offloader._is_valid_result(
1002 release_build, gs_offloader.CTS_RESULT_PATTERN, None))
1003 self.assertFalse(gs_offloader._is_valid_result(
1004 pfq_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1005 self.assertFalse(gs_offloader._is_valid_result(
1006 trybot_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1007 self.assertFalse(gs_offloader._is_valid_result(
1008 trybot_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1009 self.assertTrue(gs_offloader._is_valid_result(
1010 release_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Ningning Xia21922c82016-07-29 11:03:15 -07001011
1012
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001013 def create_results_folder(self):
1014 """Create CTS/GTS results folders."""
Ningning Xia42111242016-06-15 14:35:58 -07001015 results_folder = tempfile.mkdtemp()
1016 host_folder = os.path.join(results_folder, 'chromeos4-row9-rack11-host22')
1017 debug_folder = os.path.join(host_folder, 'debug')
1018 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
1019 cts_result_folder = os.path.join(
1020 host_folder, 'cheets_CTS.android.dpi', 'results', 'cts-results')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001021 gts_result_folder = os.path.join(
Ilja H. Friedelbfa63142017-01-26 00:56:29 -08001022 host_folder, 'cheets_GTS.google.admin', 'results', 'android-gts')
Ningning Xia42111242016-06-15 14:35:58 -07001023 timestamp_str = '2016.04.28_01.41.44'
Ningning Xia2d88eec2016-07-25 23:18:46 -07001024 timestamp_cts_folder = os.path.join(cts_result_folder, timestamp_str)
1025 timestamp_gts_folder = os.path.join(gts_result_folder, timestamp_str)
1026
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001027 # Test results in cts_result_folder with a different time-stamp.
1028 timestamp_str_2 = '2016.04.28_10.41.44'
1029 timestamp_cts_folder_2 = os.path.join(cts_result_folder, timestamp_str_2)
1030
Ningning Xia2d88eec2016-07-25 23:18:46 -07001031 for folder in [debug_folder, sysinfo_folder, cts_result_folder,
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001032 timestamp_cts_folder, timestamp_gts_folder, timestamp_cts_folder_2]:
Ningning Xia42111242016-06-15 14:35:58 -07001033 os.makedirs(folder)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001034
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001035 path_pattern_pair = [(timestamp_cts_folder, gs_offloader.CTS_RESULT_PATTERN),
1036 (timestamp_cts_folder_2, gs_offloader.CTS_RESULT_PATTERN),
1037 (timestamp_gts_folder, gs_offloader.GTS_RESULT_PATTERN)]
1038
1039 # Create timestamp.zip file_path.
Ningning Xia2d88eec2016-07-25 23:18:46 -07001040 cts_zip_file = os.path.join(cts_result_folder, timestamp_str + '.zip')
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001041 cts_zip_file_2 = os.path.join(cts_result_folder, timestamp_str_2 + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001042 gts_zip_file = os.path.join(gts_result_folder, timestamp_str + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001043
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001044 # Create xml file_path.
1045 cts_result_file = os.path.join(timestamp_cts_folder, 'testResult.xml')
1046 cts_result_file_2 = os.path.join(timestamp_cts_folder_2, 'testResult.xml')
Ilja H. Friedelbfa63142017-01-26 00:56:29 -08001047 gts_result_file = os.path.join(timestamp_gts_folder, 'test_result.xml')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001048
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001049 for file_path in [cts_zip_file, cts_zip_file_2, gts_zip_file,
1050 cts_result_file, cts_result_file_2, gts_result_file]:
1051 with open(file_path, 'w') as f:
1052 f.write('test')
Ningning Xia42111242016-06-15 14:35:58 -07001053
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001054 return (results_folder, host_folder, path_pattern_pair)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001055
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001056
1057 def test_upload_testresult_files(self):
1058 """Test upload_testresult_files."""
1059 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
1060
1061 self.mox.StubOutWithMock(gs_offloader, '_upload_files')
1062 gs_offloader._upload_files(
1063 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1064 ['test', '-d', host_folder])
1065 gs_offloader._upload_files(
1066 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1067 ['test', '-d', host_folder])
1068 gs_offloader._upload_files(
1069 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1070 ['test', '-d', host_folder])
Ningning Xia2d88eec2016-07-25 23:18:46 -07001071
Ningning Xia42111242016-06-15 14:35:58 -07001072 self.mox.ReplayAll()
1073 gs_offloader.upload_testresult_files(results_folder, False)
1074 self.mox.VerifyAll()
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001075 shutil.rmtree(results_folder)
1076
1077
1078 def test_upload_files(self):
1079 """Test upload_files"""
1080 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
1081
1082 for path, pattern in path_pattern_pair:
1083 models.test.parse_job_keyval(mox.IgnoreArg()).AndReturn({
1084 'build': 'veyron_minnie-cheets-release/R52-8248.0.0',
Ningning Xia8db632f2016-08-19 11:01:35 -07001085 'parent_job_id': 'p_id',
1086 'suite': 'arc-cts'
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001087 })
1088
1089 gs_offloader.get_cmd_list(
1090 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
1091 ['test', '-d', path])
1092 gs_offloader.get_cmd_list(
1093 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
1094 ['test', '-d', path])
1095
1096 self.mox.ReplayAll()
1097 gs_offloader._upload_files(host_folder, path, pattern, False)
1098 self.mox.VerifyAll()
1099 self.mox.ResetAll()
Ningning Xia42111242016-06-15 14:35:58 -07001100
1101 shutil.rmtree(results_folder)
Dan Shi1b4c7c32015-10-05 10:38:57 -07001102
Michael Tang97d188c2016-06-25 11:18:42 -07001103
J. Richard Barnetteea785362014-03-17 16:00:53 -07001104class JobDirectoryOffloadTests(_TempResultsDirTestBase):
1105 """Tests for `_JobDirectory.enqueue_offload()`.
1106
1107 When testing with a `days_old` parameter of 0, we use
1108 `set_finished()` instead of `set_expired()`. This causes the
1109 job's timestamp to be set in the future. This is done so as
1110 to test that when `days_old` is 0, the job is always treated
1111 as eligible for offload, regardless of the timestamp's value.
1112
1113 Testing covers the following assertions:
1114 A. Each time `enqueue_offload()` is called, a message that
1115 includes the job's directory name will be logged using
1116 `logging.debug()`, regardless of whether the job was
1117 enqueued. Nothing else is allowed to be logged.
1118 B. If the job is not eligible to be offloaded,
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001119 `get_failure_time()` and `get_failure_count()` are 0.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001120 C. If the job is not eligible for offload, nothing is
1121 enqueued in `queue`.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001122 D. When the job is offloaded, `get_failure_count()` increments
J. Richard Barnetteea785362014-03-17 16:00:53 -07001123 each time.
1124 E. When the job is offloaded, the appropriate parameters are
1125 enqueued exactly once.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001126 F. The first time a job is offloaded, `get_failure_time()` is
J. Richard Barnetteea785362014-03-17 16:00:53 -07001127 set to the current time.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001128 G. `get_failure_time()` only changes the first time that the
J. Richard Barnetteea785362014-03-17 16:00:53 -07001129 job is offloaded.
1130
1131 The test cases below are designed to exercise all of the
1132 meaningful state transitions at least once.
1133
1134 """
1135
1136 def setUp(self):
1137 super(JobDirectoryOffloadTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001138 self._job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001139 self._queue = Queue.Queue()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001140
Jakob Juelich24f22c22014-09-26 11:46:11 -07001141
J. Richard Barnetteea785362014-03-17 16:00:53 -07001142 def _offload_unexpired_job(self, days_old):
1143 """Make calls to `enqueue_offload()` for an unexpired job.
1144
1145 This method tests assertions B and C that calling
1146 `enqueue_offload()` has no effect.
1147
1148 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001149 self.assertEqual(self._job.get_failure_count(), 0)
1150 self.assertEqual(self._job.get_failure_time(), 0)
1151 self._job.enqueue_offload(self._queue, days_old)
1152 self._job.enqueue_offload(self._queue, days_old)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001153 self.assertTrue(self._queue.empty())
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001154 self.assertEqual(self._job.get_failure_count(), 0)
1155 self.assertEqual(self._job.get_failure_time(), 0)
1156 self.assertFalse(self._job.is_reportable())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001157
Jakob Juelich24f22c22014-09-26 11:46:11 -07001158
J. Richard Barnetteea785362014-03-17 16:00:53 -07001159 def _offload_expired_once(self, days_old, count):
1160 """Make one call to `enqueue_offload()` for an expired job.
1161
1162 This method tests assertions D and E regarding side-effects
1163 expected when a job is offloaded.
1164
1165 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001166 self._job.enqueue_offload(self._queue, days_old)
1167 self.assertEqual(self._job.get_failure_count(), count)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001168 self.assertFalse(self._queue.empty())
1169 v = self._queue.get_nowait()
1170 self.assertTrue(self._queue.empty())
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -07001171 self.assertEqual(v, self._job.queue_args)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001172
Jakob Juelich24f22c22014-09-26 11:46:11 -07001173
J. Richard Barnetteea785362014-03-17 16:00:53 -07001174 def _offload_expired_job(self, days_old):
1175 """Make calls to `enqueue_offload()` for a just-expired job.
1176
1177 This method directly tests assertions F and G regarding
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001178 side-effects on `get_failure_time()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001179
1180 """
1181 t0 = time.time()
1182 self._offload_expired_once(days_old, 1)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001183 self.assertFalse(self._job.is_reportable())
1184 t1 = self._job.get_failure_time()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001185 self.assertLessEqual(t1, time.time())
1186 self.assertGreaterEqual(t1, t0)
1187 self._offload_expired_once(days_old, 2)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001188 self.assertTrue(self._job.is_reportable())
1189 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001190 self._offload_expired_once(days_old, 3)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001191 self.assertTrue(self._job.is_reportable())
1192 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001193
Jakob Juelich24f22c22014-09-26 11:46:11 -07001194
J. Richard Barnetteea785362014-03-17 16:00:53 -07001195 def test_case_1_no_expiration(self):
1196 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1197
1198 This tests that offload works as expected if calls are
1199 made both before and after the job becomes expired.
1200
1201 """
1202 self._offload_unexpired_job(0)
1203 self._job.set_finished(0)
1204 self._offload_expired_job(0)
1205
Jakob Juelich24f22c22014-09-26 11:46:11 -07001206
J. Richard Barnetteea785362014-03-17 16:00:53 -07001207 def test_case_2_no_expiration(self):
1208 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1209
1210 This tests that offload works as expected if calls are made
1211 only after the job becomes expired.
1212
1213 """
1214 self._job.set_finished(0)
1215 self._offload_expired_job(0)
1216
Jakob Juelich24f22c22014-09-26 11:46:11 -07001217
J. Richard Barnetteea785362014-03-17 16:00:53 -07001218 def test_case_1_with_expiration(self):
1219 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1220
1221 This tests that offload works as expected if calls are made
1222 before the job finishes, before the job expires, and after
1223 the job expires.
1224
1225 """
1226 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1227 self._job.set_finished(_TEST_EXPIRATION_AGE)
1228 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1229 self._job.set_expired(_TEST_EXPIRATION_AGE)
1230 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1231
Jakob Juelich24f22c22014-09-26 11:46:11 -07001232
J. Richard Barnetteea785362014-03-17 16:00:53 -07001233 def test_case_2_with_expiration(self):
1234 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1235
1236 This tests that offload works as expected if calls are made
1237 between finishing and expiration, and after the job expires.
1238
1239 """
1240 self._job.set_finished(_TEST_EXPIRATION_AGE)
1241 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1242 self._job.set_expired(_TEST_EXPIRATION_AGE)
1243 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1244
Jakob Juelich24f22c22014-09-26 11:46:11 -07001245
J. Richard Barnetteea785362014-03-17 16:00:53 -07001246 def test_case_3_with_expiration(self):
1247 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1248
1249 This tests that offload works as expected if calls are made
1250 only before finishing and after expiration.
1251
1252 """
1253 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1254 self._job.set_expired(_TEST_EXPIRATION_AGE)
1255 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1256
Jakob Juelich24f22c22014-09-26 11:46:11 -07001257
J. Richard Barnetteea785362014-03-17 16:00:53 -07001258 def test_case_4_with_expiration(self):
1259 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1260
1261 This tests that offload works as expected if calls are made
1262 only after expiration.
1263
1264 """
1265 self._job.set_expired(_TEST_EXPIRATION_AGE)
1266 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1267
1268
1269class GetJobDirectoriesTests(_TempResultsDirTestBase):
1270 """Tests for `_JobDirectory.get_job_directories()`."""
1271
J. Richard Barnetteea785362014-03-17 16:00:53 -07001272 def setUp(self):
1273 super(GetJobDirectoriesTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001274 self.make_job_hierarchy()
1275 os.mkdir('not-a-job')
1276 open('not-a-dir', 'w').close()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001277
Jakob Juelich24f22c22014-09-26 11:46:11 -07001278
J. Richard Barnetteea785362014-03-17 16:00:53 -07001279 def _run_get_directories(self, cls, expected_list):
1280 """Test `get_job_directories()` for the given class.
1281
1282 Calls the method, and asserts that the returned list of
1283 directories matches the expected return value.
1284
1285 @param expected_list Expected return value from the call.
1286 """
J. Richard Barnetteea785362014-03-17 16:00:53 -07001287 dirlist = cls.get_job_directories()
1288 self.assertEqual(set(dirlist), set(expected_list))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001289
Jakob Juelich24f22c22014-09-26 11:46:11 -07001290
J. Richard Barnetteea785362014-03-17 16:00:53 -07001291 def test_get_regular_jobs(self):
1292 """Test `RegularJobDirectory.get_job_directories()`."""
1293 self._run_get_directories(job_directories.RegularJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001294 self.REGULAR_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001295
Jakob Juelich24f22c22014-09-26 11:46:11 -07001296
J. Richard Barnetteea785362014-03-17 16:00:53 -07001297 def test_get_special_jobs(self):
1298 """Test `SpecialJobDirectory.get_job_directories()`."""
1299 self._run_get_directories(job_directories.SpecialJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001300 self.SPECIAL_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001301
1302
1303class AddJobsTests(_TempResultsDirTestBase):
1304 """Tests for `Offloader._add_new_jobs()`."""
1305
J. Richard Barnette08800322014-05-16 14:49:46 -07001306 MOREJOBS = ['115-fubar', '116-fubar', '117-fubar', '118-snafu']
J. Richard Barnetteea785362014-03-17 16:00:53 -07001307
1308 def setUp(self):
1309 super(AddJobsTests, self).setUp()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001310 self._initial_job_names = (
1311 set(self.REGULAR_JOBLIST) | set(self.SPECIAL_JOBLIST))
J. Richard Barnette08800322014-05-16 14:49:46 -07001312 self.make_job_hierarchy()
1313 self._offloader = gs_offloader.Offloader(_get_options(['-a']))
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001314 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001315
Jakob Juelich24f22c22014-09-26 11:46:11 -07001316
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001317 def _run_add_new_jobs(self, expected_key_set):
J. Richard Barnetteea785362014-03-17 16:00:53 -07001318 """Basic test assertions for `_add_new_jobs()`.
1319
1320 Asserts the following:
1321 * The keys in the offloader's `_open_jobs` dictionary
1322 matches the expected set of keys.
1323 * For every job in `_open_jobs`, the job has the expected
1324 directory name.
1325
1326 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001327 count = len(expected_key_set) - len(self._offloader._open_jobs)
1328 logging.debug(mox.IgnoreArg(), count)
1329 self.mox.ReplayAll()
1330 self._offloader._add_new_jobs()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001331 self.assertEqual(expected_key_set,
1332 set(self._offloader._open_jobs.keys()))
1333 for jobkey, job in self._offloader._open_jobs.items():
1334 self.assertEqual(jobkey, job._dirname)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001335 self.mox.VerifyAll()
1336 self.mox.ResetAll()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001337
Jakob Juelich24f22c22014-09-26 11:46:11 -07001338
J. Richard Barnetteea785362014-03-17 16:00:53 -07001339 def test_add_jobs_empty(self):
1340 """Test adding jobs to an empty dictionary.
1341
1342 Calls the offloader's `_add_new_jobs()`, then perform
1343 the assertions of `self._check_open_jobs()`.
1344
1345 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001346 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001347
Jakob Juelich24f22c22014-09-26 11:46:11 -07001348
J. Richard Barnetteea785362014-03-17 16:00:53 -07001349 def test_add_jobs_non_empty(self):
1350 """Test adding jobs to a non-empty dictionary.
1351
1352 Calls the offloader's `_add_new_jobs()` twice; once from
1353 initial conditions, and then again after adding more
1354 directories. After the second call, perform the assertions
1355 of `self._check_open_jobs()`. Additionally, assert that
1356 keys added by the first call still map to their original
1357 job object after the second call.
1358
1359 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001360 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001361 jobs_copy = self._offloader._open_jobs.copy()
J. Richard Barnette08800322014-05-16 14:49:46 -07001362 for d in self.MOREJOBS:
1363 os.mkdir(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001364 self._run_add_new_jobs(self._initial_job_names |
1365 set(self.MOREJOBS))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001366 for key in jobs_copy.keys():
1367 self.assertIs(jobs_copy[key],
1368 self._offloader._open_jobs[key])
1369
1370
1371class JobStateTests(_TempResultsDirTestBase):
1372 """Tests for job state predicates.
1373
1374 This tests for the expected results from the
1375 `is_offloaded()` and `is_reportable()` predicate
1376 methods.
1377
1378 """
1379
1380 def test_unfinished_job(self):
1381 """Test that an unfinished job reports the correct state.
1382
1383 A job is "unfinished" if it isn't marked complete in the
1384 database. A job in this state is neither "complete" nor
1385 "reportable".
1386
1387 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001388 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001389 self.assertFalse(job.is_offloaded())
1390 self.assertFalse(job.is_reportable())
1391
Jakob Juelich24f22c22014-09-26 11:46:11 -07001392
J. Richard Barnetteea785362014-03-17 16:00:53 -07001393 def test_incomplete_job(self):
1394 """Test that an incomplete job reports the correct state.
1395
1396 A job is "incomplete" if exactly one attempt has been made
1397 to offload the job, but its results directory still exists.
1398 A job in this state is neither "complete" nor "reportable".
1399
1400 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001401 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001402 job.set_incomplete()
1403 self.assertFalse(job.is_offloaded())
1404 self.assertFalse(job.is_reportable())
1405
Jakob Juelich24f22c22014-09-26 11:46:11 -07001406
J. Richard Barnetteea785362014-03-17 16:00:53 -07001407 def test_reportable_job(self):
1408 """Test that a reportable job reports the correct state.
1409
1410 A job is "reportable" if more than one attempt has been made
1411 to offload the job, and its results directory still exists.
1412 A job in this state is "reportable", but not "complete".
1413
1414 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001415 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001416 job.set_reportable()
1417 self.assertFalse(job.is_offloaded())
1418 self.assertTrue(job.is_reportable())
1419
Jakob Juelich24f22c22014-09-26 11:46:11 -07001420
J. Richard Barnetteea785362014-03-17 16:00:53 -07001421 def test_completed_job(self):
1422 """Test that a completed job reports the correct state.
1423
1424 A job is "completed" if at least one attempt has been made
1425 to offload the job, and its results directory still exists.
1426 A job in this state is "complete", and not "reportable".
1427
1428 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001429 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001430 job.set_complete()
1431 self.assertTrue(job.is_offloaded())
1432 self.assertFalse(job.is_reportable())
1433
1434
1435class ReportingTests(_TempResultsDirTestBase):
1436 """Tests for `Offloader._update_offload_results()`."""
1437
J. Richard Barnetteea785362014-03-17 16:00:53 -07001438 def setUp(self):
1439 super(ReportingTests, self).setUp()
1440 self._offloader = gs_offloader.Offloader(_get_options([]))
1441 self.mox.StubOutWithMock(email_manager.manager,
1442 'send_email')
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001443 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001444
Jakob Juelich24f22c22014-09-26 11:46:11 -07001445
J. Richard Barnetteea785362014-03-17 16:00:53 -07001446 def _add_job(self, jobdir):
1447 """Add a job to the dictionary of unfinished jobs."""
1448 j = self.make_job(jobdir)
1449 self._offloader._open_jobs[j._dirname] = j
1450 return j
1451
Jakob Juelich24f22c22014-09-26 11:46:11 -07001452
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001453 def _expect_log_message(self, new_open_jobs, with_failures):
1454 """Mock expected logging calls.
1455
1456 `_update_offload_results()` logs one message with the number
1457 of jobs removed from the open job set and the number of jobs
1458 still remaining. Additionally, if there are reportable
1459 jobs, then it logs the number of jobs that haven't yet
1460 offloaded.
1461
1462 This sets up the logging calls using `new_open_jobs` to
1463 figure the job counts. If `with_failures` is true, then
1464 the log message is set up assuming that all jobs in
1465 `new_open_jobs` have offload failures.
1466
1467 @param new_open_jobs New job set for calculating counts
1468 in the messages.
1469 @param with_failures Whether the log message with a
1470 failure count is expected.
1471
1472 """
1473 count = len(self._offloader._open_jobs) - len(new_open_jobs)
1474 logging.debug(mox.IgnoreArg(), count, len(new_open_jobs))
1475 if with_failures:
1476 logging.debug(mox.IgnoreArg(), len(new_open_jobs))
1477
Jakob Juelich24f22c22014-09-26 11:46:11 -07001478
J. Richard Barnetteea785362014-03-17 16:00:53 -07001479 def _run_update_no_report(self, new_open_jobs):
1480 """Call `_update_offload_results()` expecting no report.
1481
1482 Initial conditions are set up by the caller. This calls
1483 `_update_offload_results()` once, and then checks these
1484 assertions:
1485 * The offloader's `_next_report_time` field is unchanged.
1486 * The offloader's new `_open_jobs` field contains only
1487 the entries in `new_open_jobs`.
1488 * The email_manager's `send_email` stub wasn't called.
1489
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()
1495 next_report_time = self._offloader._next_report_time
1496 self._offloader._update_offload_results()
1497 self.assertEqual(next_report_time,
1498 self._offloader._next_report_time)
1499 self.assertEqual(self._offloader._open_jobs, new_open_jobs)
1500 self.mox.VerifyAll()
1501 self.mox.ResetAll()
1502
Jakob Juelich24f22c22014-09-26 11:46:11 -07001503
J. Richard Barnetteea785362014-03-17 16:00:53 -07001504 def _run_update_with_report(self, new_open_jobs):
1505 """Call `_update_offload_results()` expecting an e-mail report.
1506
1507 Initial conditions are set up by the caller. This calls
1508 `_update_offload_results()` once, and then checks these
1509 assertions:
1510 * The offloader's `_next_report_time` field is updated
1511 to an appropriate new time.
1512 * The offloader's new `_open_jobs` field contains only
1513 the entries in `new_open_jobs`.
1514 * The email_manager's `send_email` stub was called.
1515
1516 @param new_open_jobs A dictionary representing the expected
1517 new value of the offloader's
1518 `_open_jobs` field.
1519 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001520 logging.debug(mox.IgnoreArg())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001521 email_manager.manager.send_email(
1522 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg())
1523 self.mox.ReplayAll()
1524 t0 = time.time() + gs_offloader.REPORT_INTERVAL_SECS
1525 self._offloader._update_offload_results()
1526 t1 = time.time() + gs_offloader.REPORT_INTERVAL_SECS
1527 next_report_time = self._offloader._next_report_time
1528 self.assertGreaterEqual(next_report_time, t0)
1529 self.assertLessEqual(next_report_time, t1)
1530 self.assertEqual(self._offloader._open_jobs, new_open_jobs)
1531 self.mox.VerifyAll()
1532 self.mox.ResetAll()
1533
Jakob Juelich24f22c22014-09-26 11:46:11 -07001534
J. Richard Barnetteea785362014-03-17 16:00:53 -07001535 def test_no_jobs(self):
1536 """Test `_update_offload_results()` with no open jobs.
1537
1538 Initial conditions are an empty `_open_jobs` list and
1539 `_next_report_time` in the past. Expected result is no
1540 e-mail report, and an empty `_open_jobs` list.
1541
1542 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001543 self._expect_log_message({}, False)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001544 self._run_update_no_report({})
1545
Jakob Juelich24f22c22014-09-26 11:46:11 -07001546
J. Richard Barnetteea785362014-03-17 16:00:53 -07001547 def test_all_completed(self):
1548 """Test `_update_offload_results()` with only complete jobs.
1549
1550 Initial conditions are an `_open_jobs` list consisting of
1551 only completed jobs and `_next_report_time` in the past.
1552 Expected result is no e-mail report, and an empty
1553 `_open_jobs` list.
1554
1555 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001556 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001557 self._add_job(d).set_complete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001558 self._expect_log_message({}, False)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001559 self._run_update_no_report({})
1560
Jakob Juelich24f22c22014-09-26 11:46:11 -07001561
J. Richard Barnetteea785362014-03-17 16:00:53 -07001562 def test_none_finished(self):
1563 """Test `_update_offload_results()` with only unfinished jobs.
1564
1565 Initial conditions are an `_open_jobs` list consisting of
1566 only unfinished jobs and `_next_report_time` in the past.
1567 Expected result is no e-mail report, and no change to the
1568 `_open_jobs` list.
1569
1570 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001571 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001572 self._add_job(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001573 new_jobs = self._offloader._open_jobs.copy()
1574 self._expect_log_message(new_jobs, False)
1575 self._run_update_no_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001576
Jakob Juelich24f22c22014-09-26 11:46:11 -07001577
J. Richard Barnetteea785362014-03-17 16:00:53 -07001578 def test_none_reportable(self):
1579 """Test `_update_offload_results()` with only incomplete jobs.
1580
1581 Initial conditions are an `_open_jobs` list consisting of
1582 only incomplete jobs and `_next_report_time` in the past.
1583 Expected result is no e-mail report, and no change to the
1584 `_open_jobs` list.
1585
1586 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001587 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001588 self._add_job(d).set_incomplete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001589 new_jobs = self._offloader._open_jobs.copy()
1590 self._expect_log_message(new_jobs, False)
1591 self._run_update_no_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001592
Jakob Juelich24f22c22014-09-26 11:46:11 -07001593
J. Richard Barnetteea785362014-03-17 16:00:53 -07001594 def test_report_not_ready(self):
1595 """Test `_update_offload_results()` e-mail throttling.
1596
1597 Initial conditions are an `_open_jobs` list consisting of
1598 only reportable jobs but with `_next_report_time` in
1599 the future. Expected result is no e-mail report, and no
1600 change to the `_open_jobs` list.
1601
1602 """
1603 # N.B. This test may fail if its run time exceeds more than
1604 # about _MARGIN_SECS seconds.
J. Richard Barnette08800322014-05-16 14:49:46 -07001605 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001606 self._add_job(d).set_reportable()
1607 self._offloader._next_report_time += _MARGIN_SECS
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001608 new_jobs = self._offloader._open_jobs.copy()
1609 self._expect_log_message(new_jobs, True)
1610 self._run_update_no_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001611
Jakob Juelich24f22c22014-09-26 11:46:11 -07001612
J. Richard Barnetteea785362014-03-17 16:00:53 -07001613 def test_reportable(self):
1614 """Test `_update_offload_results()` with reportable jobs.
1615
1616 Initial conditions are an `_open_jobs` list consisting of
1617 only reportable jobs and with `_next_report_time` in
1618 the past. Expected result is an e-mail report, and no
1619 change to the `_open_jobs` list.
1620
1621 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001622 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001623 self._add_job(d).set_reportable()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001624 new_jobs = self._offloader._open_jobs.copy()
1625 self._expect_log_message(new_jobs, True)
1626 self._run_update_with_report(new_jobs)
1627
Jakob Juelich24f22c22014-09-26 11:46:11 -07001628
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001629 def test_reportable_mixed(self):
1630 """Test `_update_offload_results()` with a mixture of jobs.
1631
1632 Initial conditions are an `_open_jobs` list consisting of
1633 one reportable jobs and the remainder of the jobs
1634 incomplete. The value of `_next_report_time` is in the
1635 past. Expected result is an e-mail report that includes
1636 both the reportable and the incomplete jobs, and no change
1637 to the `_open_jobs` list.
1638
1639 """
1640 self._add_job(self.REGULAR_JOBLIST[0]).set_reportable()
1641 for d in self.REGULAR_JOBLIST[1:]:
1642 self._add_job(d).set_incomplete()
1643 new_jobs = self._offloader._open_jobs.copy()
1644 self._expect_log_message(new_jobs, True)
1645 self._run_update_with_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001646
1647
1648if __name__ == '__main__':
1649 unittest.main()