blob: d59d746a2621825f10c333a802f6d9d918047ec3 [file] [log] [blame]
Allen Li4ad0c3b2017-05-05 13:12:11 -07001#!/usr/bin/python
Michael Tang97d188c2016-06-25 11:18:42 -07002# Copyright 2016 The Chromium OS Authors. All rights reserved.
J. Richard Barnetteea785362014-03-17 16:00:53 -07003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Keith Haddow5ba5fb82016-11-09 11:39:36 -08006import __builtin__
J. Richard Barnetteea785362014-03-17 16:00:53 -07007import Queue
Michael Tang97d188c2016-06-25 11:18:42 -07008import base64
J. Richard Barnetteea785362014-03-17 16:00:53 -07009import datetime
10import logging
11import os
12import shutil
J. Richard Barnette2e443ef2014-05-20 12:31:35 -070013import signal
Laurence Goodbyca7726d2017-02-14 17:09:07 -080014import stat
J. Richard Barnetteea785362014-03-17 16:00:53 -070015import sys
16import tempfile
17import time
18import unittest
19
Allen Li9579b382017-05-05 17:07:43 -070020import mock
J. Richard Barnetteea785362014-03-17 16:00:53 -070021import mox
22
23import common
Allen Li5ed7e632017-02-03 16:31:33 -080024from autotest_lib.client.common_lib import global_config
Dan Shi1b4c7c32015-10-05 10:38:57 -070025from autotest_lib.client.common_lib import time_utils
26from autotest_lib.client.common_lib import utils
Prathmesh Prabhubeb9e012017-01-30 16:18:39 -080027from autotest_lib.site_utils import gs_offloader
28from autotest_lib.site_utils import job_directories
Ningning Xia2d981ee2016-07-06 17:59:54 -070029from autotest_lib.tko import models
Jakob Juelich24f22c22014-09-26 11:46:11 -070030
J. Richard Barnetteea785362014-03-17 16:00:53 -070031# Test value to use for `days_old`, if nothing else is required.
32_TEST_EXPIRATION_AGE = 7
33
34# When constructing sample time values for testing expiration,
35# allow this many seconds between the expiration time and the
36# current time.
37_MARGIN_SECS = 10.0
38
39
40def _get_options(argv):
41 """Helper function to exercise command line parsing.
42
43 @param argv Value of sys.argv to be parsed.
44
45 """
46 sys.argv = ['bogus.py'] + argv
47 return gs_offloader.parse_options()
48
49
Laurence Goodbyca7726d2017-02-14 17:09:07 -080050def is_fifo(path):
Allen Li93585382017-05-05 14:24:53 -070051 """Determines whether a path is a fifo.
52
53 @param path: fifo path string.
54 """
Laurence Goodbyca7726d2017-02-14 17:09:07 -080055 return stat.S_ISFIFO(os.lstat(path).st_mode)
56
57
Simran Basidd129972014-09-11 14:34:49 -070058class OffloaderOptionsTests(mox.MoxTestBase):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070059 """Tests for the `Offloader` constructor.
60
61 Tests that offloader instance fields are set as expected
62 for given command line options.
63
64 """
65
66 _REGULAR_ONLY = set([job_directories.RegularJobDirectory])
67 _SPECIAL_ONLY = set([job_directories.SpecialJobDirectory])
68 _BOTH = _REGULAR_ONLY | _SPECIAL_ONLY
J. Richard Barnetteea785362014-03-17 16:00:53 -070069
Jakob Juelich24f22c22014-09-26 11:46:11 -070070
Simran Basidd129972014-09-11 14:34:49 -070071 def setUp(self):
72 super(OffloaderOptionsTests, self).setUp()
73 self.mox.StubOutWithMock(utils, 'get_offload_gsuri')
Simran Basif3e305f2014-10-03 14:43:53 -070074 gs_offloader.GS_OFFLOADING_ENABLED = True
Michael Tang0df2eb42016-05-13 19:06:54 -070075 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
Simran Basidd129972014-09-11 14:34:49 -070076
Jakob Juelich24f22c22014-09-26 11:46:11 -070077
Michael Tang97d188c2016-06-25 11:18:42 -070078 def _mock_get_offload_func(self, is_moblab, multiprocessing=False,
Keith Haddow5ba5fb82016-11-09 11:39:36 -080079 pubsub_topic=None, delete_age=0):
Simran Basidd129972014-09-11 14:34:49 -070080 """Mock the process of getting the offload_dir function."""
81 if is_moblab:
82 expected_gsuri = '%sresults/%s/%s/' % (
83 global_config.global_config.get_config_value(
84 'CROS', 'image_storage_server'),
85 'Fa:ke:ma:c0:12:34', 'rand0m-uu1d')
86 else:
87 expected_gsuri = utils.DEFAULT_OFFLOAD_GSURI
88 utils.get_offload_gsuri().AndReturn(expected_gsuri)
Keith Haddow5ba5fb82016-11-09 11:39:36 -080089 offload_func = gs_offloader.get_offload_dir_func(expected_gsuri,
90 multiprocessing, delete_age, pubsub_topic)
Simran Basidd129972014-09-11 14:34:49 -070091 self.mox.StubOutWithMock(gs_offloader, 'get_offload_dir_func')
Michael Tang97d188c2016-06-25 11:18:42 -070092 gs_offloader.get_offload_dir_func(expected_gsuri, multiprocessing,
Keith Haddow5ba5fb82016-11-09 11:39:36 -080093 delete_age, pubsub_topic).AndReturn(offload_func)
Simran Basidd129972014-09-11 14:34:49 -070094 self.mox.ReplayAll()
95 return offload_func
96
Jakob Juelich24f22c22014-09-26 11:46:11 -070097
J. Richard Barnetteea785362014-03-17 16:00:53 -070098 def test_process_no_options(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070099 """Test default offloader options."""
Simran Basidd129972014-09-11 14:34:49 -0700100 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700101 offloader = gs_offloader.Offloader(_get_options([]))
102 self.assertEqual(set(offloader._jobdir_classes),
103 self._REGULAR_ONLY)
104 self.assertEqual(offloader._processes, 1)
105 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700106 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800107 self.assertEqual(offloader._upload_age_limit, 0)
108 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700109
Jakob Juelich24f22c22014-09-26 11:46:11 -0700110
J. Richard Barnetteea785362014-03-17 16:00:53 -0700111 def test_process_all_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700112 """Test offloader handling for the --all option."""
Simran Basidd129972014-09-11 14:34:49 -0700113 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700114 offloader = gs_offloader.Offloader(_get_options(['--all']))
115 self.assertEqual(set(offloader._jobdir_classes), self._BOTH)
116 self.assertEqual(offloader._processes, 1)
117 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700118 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800119 self.assertEqual(offloader._upload_age_limit, 0)
120 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700121
Jakob Juelich24f22c22014-09-26 11:46:11 -0700122
J. Richard Barnetteea785362014-03-17 16:00:53 -0700123 def test_process_hosts_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700124 """Test offloader handling for the --hosts option."""
Simran Basidd129972014-09-11 14:34:49 -0700125 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700126 offloader = gs_offloader.Offloader(
127 _get_options(['--hosts']))
128 self.assertEqual(set(offloader._jobdir_classes),
129 self._SPECIAL_ONLY)
130 self.assertEqual(offloader._processes, 1)
131 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700132 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800133 self.assertEqual(offloader._upload_age_limit, 0)
134 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700135
Jakob Juelich24f22c22014-09-26 11:46:11 -0700136
J. Richard Barnetteea785362014-03-17 16:00:53 -0700137 def test_parallelism_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700138 """Test offloader handling for the --parallelism option."""
Simran Basidd129972014-09-11 14:34:49 -0700139 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700140 offloader = gs_offloader.Offloader(
141 _get_options(['--parallelism', '2']))
142 self.assertEqual(set(offloader._jobdir_classes),
143 self._REGULAR_ONLY)
144 self.assertEqual(offloader._processes, 2)
145 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700146 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800147 self.assertEqual(offloader._upload_age_limit, 0)
148 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700149
Jakob Juelich24f22c22014-09-26 11:46:11 -0700150
J. Richard Barnetteea785362014-03-17 16:00:53 -0700151 def test_delete_only_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700152 """Test offloader handling for the --delete_only option."""
153 offloader = gs_offloader.Offloader(
154 _get_options(['--delete_only']))
155 self.assertEqual(set(offloader._jobdir_classes),
156 self._REGULAR_ONLY)
157 self.assertEqual(offloader._processes, 1)
158 self.assertEqual(offloader._offload_func,
159 gs_offloader.delete_files)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800160 self.assertEqual(offloader._upload_age_limit, 0)
161 self.assertEqual(offloader._delete_age_limit, 0)
Michael Tang97d188c2016-06-25 11:18:42 -0700162 self.assertIsNone(offloader._pubsub_topic)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700163
Jakob Juelich24f22c22014-09-26 11:46:11 -0700164
Simran Basidf4751e2014-10-10 14:19:22 -0700165 def test_days_old_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700166 """Test offloader handling for the --days_old option."""
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800167 offload_func = self._mock_get_offload_func(False, delete_age=7)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700168 offloader = gs_offloader.Offloader(
169 _get_options(['--days_old', '7']))
170 self.assertEqual(set(offloader._jobdir_classes),
171 self._REGULAR_ONLY)
172 self.assertEqual(offloader._processes, 1)
173 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700174 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800175 self.assertEqual(offloader._upload_age_limit, 7)
176 self.assertEqual(offloader._delete_age_limit, 7)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700177
Jakob Juelich24f22c22014-09-26 11:46:11 -0700178
Simran Basidd129972014-09-11 14:34:49 -0700179 def test_moblab_gsuri_generation(self):
180 """Test offloader construction for Moblab."""
181 offload_func = self._mock_get_offload_func(True)
182 offloader = gs_offloader.Offloader(_get_options([]))
183 self.assertEqual(set(offloader._jobdir_classes),
184 self._REGULAR_ONLY)
185 self.assertEqual(offloader._processes, 1)
186 self.assertEqual(offloader._offload_func,
187 offload_func)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800188 self.assertEqual(offloader._upload_age_limit, 0)
189 self.assertEqual(offloader._delete_age_limit, 0)
Simran Basidd129972014-09-11 14:34:49 -0700190
J. Richard Barnetteea785362014-03-17 16:00:53 -0700191
Simran Basif3e305f2014-10-03 14:43:53 -0700192 def test_globalconfig_offloading_flag(self):
193 """Test enabling of --delete_only via global_config."""
194 gs_offloader.GS_OFFLOADING_ENABLED = False
195 offloader = gs_offloader.Offloader(
196 _get_options([]))
197 self.assertEqual(offloader._offload_func,
198 gs_offloader.delete_files)
199
Michael Tang0df2eb42016-05-13 19:06:54 -0700200 def test_offloader_multiprocessing_flag_set(self):
201 """Test multiprocessing is set."""
202 offload_func = self._mock_get_offload_func(True, True)
203 offloader = gs_offloader.Offloader(_get_options(['-m']))
204 self.assertEqual(offloader._offload_func,
205 offload_func)
206 self.mox.VerifyAll()
207
208 def test_offloader_multiprocessing_flag_not_set_default_false(self):
209 """Test multiprocessing is set."""
210 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
211 offload_func = self._mock_get_offload_func(True, False)
212 offloader = gs_offloader.Offloader(_get_options([]))
213 self.assertEqual(offloader._offload_func,
214 offload_func)
215 self.mox.VerifyAll()
216
217 def test_offloader_multiprocessing_flag_not_set_default_true(self):
218 """Test multiprocessing is set."""
219 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = True
220 offload_func = self._mock_get_offload_func(True, True)
221 offloader = gs_offloader.Offloader(_get_options([]))
222 self.assertEqual(offloader._offload_func,
223 offload_func)
224 self.mox.VerifyAll()
225
Michael Tang97d188c2016-06-25 11:18:42 -0700226 def test_offloader_pubsub_topic_not_set(self):
227 """Test multiprocessing is set."""
228 offload_func = self._mock_get_offload_func(True, False)
229 offloader = gs_offloader.Offloader(_get_options([]))
230 self.assertEqual(offloader._offload_func,
231 offload_func)
232 self.mox.VerifyAll()
233
234 def test_offloader_pubsub_topic_set(self):
235 """Test multiprocessing is set."""
236 offload_func = self._mock_get_offload_func(True, False, 'test-topic')
237 offloader = gs_offloader.Offloader(_get_options(['-t', 'test-topic']))
238 self.assertEqual(offloader._offload_func,
239 offload_func)
240 self.mox.VerifyAll()
241
Simran Basif3e305f2014-10-03 14:43:53 -0700242
J. Richard Barnetteea785362014-03-17 16:00:53 -0700243def _make_timestamp(age_limit, is_expired):
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800244 """Create a timestamp for use by `job_directories.is_job_expired()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -0700245
246 The timestamp will meet the syntactic requirements for
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800247 timestamps used as input to `is_job_expired()`. If
J. Richard Barnetteea785362014-03-17 16:00:53 -0700248 `is_expired` is true, the timestamp will be older than
249 `age_limit` days before the current time; otherwise, the
250 date will be younger.
251
252 @param age_limit The number of days before expiration of the
253 target timestamp.
254 @param is_expired Whether the timestamp should be expired
255 relative to `age_limit`.
256
257 """
258 seconds = -_MARGIN_SECS
259 if is_expired:
260 seconds = -seconds
261 delta = datetime.timedelta(days=age_limit, seconds=seconds)
262 reference_time = datetime.datetime.now() - delta
Dan Shidfea3682014-08-10 23:38:40 -0700263 return reference_time.strftime(time_utils.TIME_FMT)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700264
265
266class JobExpirationTests(unittest.TestCase):
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800267 """Tests to exercise `job_directories.is_job_expired()`."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700268
269 def test_expired(self):
270 """Test detection of an expired job."""
271 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, True)
272 self.assertTrue(
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
277 def test_alive(self):
278 """Test detection of a job that's not expired."""
279 # N.B. This test may fail if its run time exceeds more than
280 # about _MARGIN_SECS seconds.
281 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, False)
282 self.assertFalse(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800283 job_directories.is_job_expired(
J. Richard Barnetteea785362014-03-17 16:00:53 -0700284 _TEST_EXPIRATION_AGE, timestamp))
285
286
287class _MockJobDirectory(job_directories._JobDirectory):
288 """Subclass of `_JobDirectory` used as a helper for tests."""
289
290 GLOB_PATTERN = '[0-9]*-*'
291
Jakob Juelich24f22c22014-09-26 11:46:11 -0700292
J. Richard Barnetteea785362014-03-17 16:00:53 -0700293 def __init__(self, resultsdir):
294 """Create new job in initial state."""
295 super(_MockJobDirectory, self).__init__(resultsdir)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700296 self._timestamp = None
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800297 self.queue_args = [resultsdir, os.path.dirname(resultsdir), self._timestamp]
J. Richard Barnetteea785362014-03-17 16:00:53 -0700298
Jakob Juelich24f22c22014-09-26 11:46:11 -0700299
J. Richard Barnetteea785362014-03-17 16:00:53 -0700300 def get_timestamp_if_finished(self):
301 return self._timestamp
302
Jakob Juelich24f22c22014-09-26 11:46:11 -0700303
J. Richard Barnetteea785362014-03-17 16:00:53 -0700304 def set_finished(self, days_old):
305 """Make this job appear to be finished.
306
307 After calling this function, calls to `enqueue_offload()`
308 will find this job as finished, but not expired and ready
309 for offload. Note that when `days_old` is 0,
310 `enqueue_offload()` will treat a finished job as eligible
311 for offload.
312
313 @param days_old The value of the `days_old` parameter that
314 will be passed to `enqueue_offload()` for
315 testing.
316
317 """
318 self._timestamp = _make_timestamp(days_old, False)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800319 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700320
Jakob Juelich24f22c22014-09-26 11:46:11 -0700321
J. Richard Barnetteea785362014-03-17 16:00:53 -0700322 def set_expired(self, days_old):
323 """Make this job eligible to be offloaded.
324
325 After calling this function, calls to `offload` will attempt
326 to offload this job.
327
328 @param days_old The value of the `days_old` parameter that
329 will be passed to `enqueue_offload()` for
330 testing.
331
332 """
333 self._timestamp = _make_timestamp(days_old, True)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800334 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700335
Jakob Juelich24f22c22014-09-26 11:46:11 -0700336
J. Richard Barnetteea785362014-03-17 16:00:53 -0700337 def set_incomplete(self):
338 """Make this job appear to have failed offload just once."""
339 self._offload_count += 1
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700340 self._first_offload_start = time.time()
J. Richard Barnetteea785362014-03-17 16:00:53 -0700341 if not os.path.isdir(self._dirname):
342 os.mkdir(self._dirname)
343
Jakob Juelich24f22c22014-09-26 11:46:11 -0700344
J. Richard Barnetteea785362014-03-17 16:00:53 -0700345 def set_reportable(self):
346 """Make this job be reportable."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700347 self.set_incomplete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700348 self._offload_count += 1
J. Richard Barnetteea785362014-03-17 16:00:53 -0700349
Jakob Juelich24f22c22014-09-26 11:46:11 -0700350
J. Richard Barnetteea785362014-03-17 16:00:53 -0700351 def set_complete(self):
352 """Make this job be completed."""
353 self._offload_count += 1
354 if os.path.isdir(self._dirname):
355 os.rmdir(self._dirname)
356
357
Simran Basi1e10e922015-04-16 15:09:56 -0700358 def process_gs_instructions(self):
359 """Always still offload the job directory."""
360 return True
361
362
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700363class CommandListTests(unittest.TestCase):
364 """Tests for `get_cmd_list()`."""
365
MK Ryue93c8572015-08-11 11:53:00 -0700366 def _command_list_assertions(self, job, use_rsync=True, multi=False):
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700367 """Call `get_cmd_list()` and check the return value.
368
369 Check the following assertions:
370 * The command name (argv[0]) is 'gsutil'.
MK Ryue93c8572015-08-11 11:53:00 -0700371 * '-m' option (argv[1]) is on when the argument, multi, is True.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700372 * The arguments contain the 'cp' subcommand.
373 * The next-to-last argument (the source directory) is the
374 job's `queue_args[0]`.
Simran Basidd129972014-09-11 14:34:49 -0700375 * The last argument (the destination URL) is the job's
376 'queue_args[1]'.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700377
378 @param job A job with properly calculated arguments to
379 `get_cmd_list()`
MK Ryue93c8572015-08-11 11:53:00 -0700380 @param use_rsync True when using 'rsync'. False when using 'cp'.
381 @param multi True when using '-m' option for gsutil.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700382
383 """
Jakob Juelich24f22c22014-09-26 11:46:11 -0700384 test_bucket_uri = 'gs://a-test-bucket'
385
386 gs_offloader.USE_RSYNC_ENABLED = use_rsync
387
388 command = gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700389 multi, job.queue_args[0],
390 os.path.join(test_bucket_uri, job.queue_args[1]))
Jakob Juelich24f22c22014-09-26 11:46:11 -0700391
Keith Haddow82ad4502017-05-16 12:59:19 -0700392 self.assertEqual(command[0], gs_offloader._GSUTIL_CMD)
MK Ryue93c8572015-08-11 11:53:00 -0700393 if multi:
394 self.assertEqual(command[1], '-m')
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700395 self.assertEqual(command[-2], job.queue_args[0])
Jakob Juelich24f22c22014-09-26 11:46:11 -0700396
397 if use_rsync:
398 self.assertTrue('rsync' in command)
399 self.assertEqual(command[-1],
400 os.path.join(test_bucket_uri, job.queue_args[0]))
401 else:
402 self.assertTrue('cp' in command)
403 self.assertEqual(command[-1],
404 os.path.join(test_bucket_uri, job.queue_args[1]))
405
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700406
407 def test_get_cmd_list_regular(self):
408 """Test `get_cmd_list()` as for a regular job."""
409 job = _MockJobDirectory('118-debug')
410 self._command_list_assertions(job)
411
Jakob Juelich24f22c22014-09-26 11:46:11 -0700412
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700413 def test_get_cmd_list_special(self):
414 """Test `get_cmd_list()` as for a special job."""
415 job = _MockJobDirectory('hosts/host1/118-reset')
416 self._command_list_assertions(job)
417
418
Jakob Juelich24f22c22014-09-26 11:46:11 -0700419 def test_get_cmd_list_regular_no_rsync(self):
420 """Test `get_cmd_list()` as for a regular job."""
421 job = _MockJobDirectory('118-debug')
422 self._command_list_assertions(job, use_rsync=False)
423
424
425 def test_get_cmd_list_special_no_rsync(self):
426 """Test `get_cmd_list()` as for a special job."""
427 job = _MockJobDirectory('hosts/host1/118-reset')
428 self._command_list_assertions(job, use_rsync=False)
429
430
MK Ryue93c8572015-08-11 11:53:00 -0700431 def test_get_cmd_list_regular_multi(self):
432 """Test `get_cmd_list()` as for a regular job with True multi."""
433 job = _MockJobDirectory('118-debug')
434 self._command_list_assertions(job, multi=True)
435
436
437 def test_get_cmd_list_special_multi(self):
438 """Test `get_cmd_list()` as for a special job with True multi."""
439 job = _MockJobDirectory('hosts/host1/118-reset')
440 self._command_list_assertions(job, multi=True)
441
442
Michael Tang97d188c2016-06-25 11:18:42 -0700443class PubSubTest(mox.MoxTestBase):
444 """Test the test result notifcation data structure."""
445
446 def test_create_test_result_notification(self):
447 """Tests the test result notification message."""
Allen Li5ed7e632017-02-03 16:31:33 -0800448 self.mox.StubOutWithMock(utils, 'get_moblab_id')
449 self.mox.StubOutWithMock(utils,
Michael Tang328073b2016-11-01 15:33:38 -0700450 'get_default_interface_mac_address')
Allen Li5ed7e632017-02-03 16:31:33 -0800451 utils.get_default_interface_mac_address().AndReturn(
Michael Tang328073b2016-11-01 15:33:38 -0700452 '1c:dc:d1:11:01:e1')
Allen Li5ed7e632017-02-03 16:31:33 -0800453 utils.get_moblab_id().AndReturn(
Michael Tang328073b2016-11-01 15:33:38 -0700454 'c8386d92-9ad1-11e6-80f5-111111111111')
455 self.mox.ReplayAll()
Michael Tangb45c7232016-11-14 21:40:43 -0800456 msg = gs_offloader._create_test_result_notification(
457 'gs://test_bucket', '123-moblab')
Michael Tang97d188c2016-06-25 11:18:42 -0700458 self.assertEquals(base64.b64encode(
459 gs_offloader.NEW_TEST_RESULT_MESSAGE), msg['data'])
Michael Tang328073b2016-11-01 15:33:38 -0700460 self.assertEquals(
461 gs_offloader.NOTIFICATION_VERSION,
462 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_VERSION])
463 self.assertEquals(
464 '1c:dc:d1:11:01:e1',
465 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_MOBLAB_MAC])
466 self.assertEquals(
467 'c8386d92-9ad1-11e6-80f5-111111111111',
468 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_MOBLAB_ID])
469 self.assertEquals(
Michael Tangb45c7232016-11-14 21:40:43 -0800470 'gs://test_bucket/123-moblab',
Michael Tang328073b2016-11-01 15:33:38 -0700471 msg['attributes'][gs_offloader.NOTIFICATION_ATTR_GCS_URI])
472 self.mox.VerifyAll()
Michael Tang97d188c2016-06-25 11:18:42 -0700473
474
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700475class _MockJob(object):
476 """Class to mock the return value of `AFE.get_jobs()`."""
477 def __init__(self, created):
478 self.created_on = created
479
480
481class _MockHostQueueEntry(object):
482 """Class to mock the return value of `AFE.get_host_queue_entries()`."""
483 def __init__(self, finished):
484 self.finished_on = finished
485
486
487class _MockSpecialTask(object):
488 """Class to mock the return value of `AFE.get_special_tasks()`."""
489 def __init__(self, finished):
490 self.time_finished = finished
491
492
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700493class JobDirectorySubclassTests(mox.MoxTestBase):
494 """Test specific to RegularJobDirectory and SpecialJobDirectory.
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700495
496 This provides coverage for the implementation in both
497 RegularJobDirectory and SpecialJobDirectory.
498
499 """
500
501 def setUp(self):
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700502 super(JobDirectorySubclassTests, self).setUp()
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700503 self.mox.StubOutWithMock(job_directories._AFE, 'get_jobs')
504 self.mox.StubOutWithMock(job_directories._AFE,
505 'get_host_queue_entries')
506 self.mox.StubOutWithMock(job_directories._AFE,
507 'get_special_tasks')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700508
Jakob Juelich24f22c22014-09-26 11:46:11 -0700509
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700510 def test_regular_job_fields(self):
511 """Test the constructor for `RegularJobDirectory`.
512
513 Construct a regular job, and assert that the `_dirname`
514 and `_id` attributes are set as expected.
515
516 """
517 resultsdir = '118-fubar'
518 job = job_directories.RegularJobDirectory(resultsdir)
519 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700520 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700521
Jakob Juelich24f22c22014-09-26 11:46:11 -0700522
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700523 def test_special_job_fields(self):
524 """Test the constructor for `SpecialJobDirectory`.
525
526 Construct a special job, and assert that the `_dirname`
527 and `_id` attributes are set as expected.
528
529 """
530 destdir = 'hosts/host1'
531 resultsdir = destdir + '/118-reset'
532 job = job_directories.SpecialJobDirectory(resultsdir)
533 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700534 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700535
Jakob Juelich24f22c22014-09-26 11:46:11 -0700536
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700537 def _check_finished_job(self, jobtime, hqetimes, expected):
538 """Mock and test behavior of a finished job.
539
540 Initialize the mocks for a call to
541 `get_timestamp_if_finished()`, then simulate one call.
542 Assert that the returned timestamp matches the passed
543 in expected value.
544
545 @param jobtime Time used to construct a _MockJob object.
546 @param hqetimes List of times used to construct
547 _MockHostQueueEntry objects.
548 @param expected Expected time to be returned by
549 get_timestamp_if_finished
550
551 """
552 job = job_directories.RegularJobDirectory('118-fubar')
553 job_directories._AFE.get_jobs(
554 id=job._id, finished=True).AndReturn(
555 [_MockJob(jobtime)])
556 job_directories._AFE.get_host_queue_entries(
557 finished_on__isnull=False,
558 job_id=job._id).AndReturn(
559 [_MockHostQueueEntry(t) for t in hqetimes])
560 self.mox.ReplayAll()
561 self.assertEqual(expected, job.get_timestamp_if_finished())
562 self.mox.VerifyAll()
563
564
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700565 def test_finished_regular_job(self):
566 """Test getting the timestamp for a finished regular job.
567
568 Tests the return value for
569 `RegularJobDirectory.get_timestamp_if_finished()` when
570 the AFE indicates the job is finished.
571
572 """
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700573 created_timestamp = _make_timestamp(1, True)
574 hqe_timestamp = _make_timestamp(0, True)
575 self._check_finished_job(created_timestamp,
576 [hqe_timestamp],
577 hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700578
Jakob Juelich24f22c22014-09-26 11:46:11 -0700579
Simran Basifb98e462014-08-18 12:35:44 -0700580 def test_finished_regular_job_multiple_hqes(self):
581 """Test getting the timestamp for a regular job with multiple hqes.
582
583 Tests the return value for
584 `RegularJobDirectory.get_timestamp_if_finished()` when
585 the AFE indicates the job is finished and the job has multiple host
586 queue entries.
587
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700588 Tests that the returned timestamp is the latest timestamp in
589 the list of HQEs, regardless of the returned order.
590
Simran Basifb98e462014-08-18 12:35:44 -0700591 """
Simran Basifb98e462014-08-18 12:35:44 -0700592 created_timestamp = _make_timestamp(2, True)
593 older_hqe_timestamp = _make_timestamp(1, True)
594 newer_hqe_timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700595 hqe_list = [older_hqe_timestamp,
596 newer_hqe_timestamp]
597 self._check_finished_job(created_timestamp,
598 hqe_list,
599 newer_hqe_timestamp)
600 self.mox.ResetAll()
601 hqe_list.reverse()
602 self._check_finished_job(created_timestamp,
603 hqe_list,
604 newer_hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700605
Jakob Juelich24f22c22014-09-26 11:46:11 -0700606
Simran Basifb98e462014-08-18 12:35:44 -0700607 def test_finished_regular_job_null_finished_times(self):
608 """Test getting the timestamp for an aborted regular job.
609
610 Tests the return value for
611 `RegularJobDirectory.get_timestamp_if_finished()` when
612 the AFE indicates the job is finished and the job has aborted host
613 queue entries.
614
615 """
Simran Basifb98e462014-08-18 12:35:44 -0700616 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700617 self._check_finished_job(timestamp, [], timestamp)
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700618
Jakob Juelich24f22c22014-09-26 11:46:11 -0700619
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700620 def test_unfinished_regular_job(self):
621 """Test getting the timestamp for an unfinished regular job.
622
623 Tests the return value for
624 `RegularJobDirectory.get_timestamp_if_finished()` when
625 the AFE indicates the job is not finished.
626
627 """
628 job = job_directories.RegularJobDirectory('118-fubar')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700629 job_directories._AFE.get_jobs(
630 id=job._id, finished=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700631 self.mox.ReplayAll()
632 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700633 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700634
Jakob Juelich24f22c22014-09-26 11:46:11 -0700635
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700636 def test_finished_special_job(self):
637 """Test getting the timestamp for a finished special job.
638
639 Tests the return value for
640 `SpecialJobDirectory.get_timestamp_if_finished()` when
641 the AFE indicates the job is finished.
642
643 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700644 job = job_directories.SpecialJobDirectory(
645 'hosts/host1/118-reset')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700646 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700647 job_directories._AFE.get_special_tasks(
648 id=job._id, is_complete=True).AndReturn(
649 [_MockSpecialTask(timestamp)])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700650 self.mox.ReplayAll()
651 self.assertEqual(timestamp,
652 job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700653 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700654
Jakob Juelich24f22c22014-09-26 11:46:11 -0700655
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700656 def test_unfinished_special_job(self):
657 """Test getting the timestamp for an unfinished special job.
658
659 Tests the return value for
660 `SpecialJobDirectory.get_timestamp_if_finished()` when
661 the AFE indicates the job is not finished.
662
663 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700664 job = job_directories.SpecialJobDirectory(
665 'hosts/host1/118-reset')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700666 job_directories._AFE.get_special_tasks(
667 id=job._id, is_complete=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700668 self.mox.ReplayAll()
669 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700670 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700671
672
J. Richard Barnetteea785362014-03-17 16:00:53 -0700673class _TempResultsDirTestBase(mox.MoxTestBase):
674 """Base class for tests using a temporary results directory."""
675
J. Richard Barnette08800322014-05-16 14:49:46 -0700676 REGULAR_JOBLIST = [
677 '111-fubar', '112-fubar', '113-fubar', '114-snafu']
678 HOST_LIST = ['host1', 'host2', 'host3']
679 SPECIAL_JOBLIST = [
680 'hosts/host1/333-reset', 'hosts/host1/334-reset',
681 'hosts/host2/444-reset', 'hosts/host3/555-reset']
682
Jakob Juelich24f22c22014-09-26 11:46:11 -0700683
J. Richard Barnetteea785362014-03-17 16:00:53 -0700684 def setUp(self):
685 super(_TempResultsDirTestBase, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -0700686 self._resultsroot = tempfile.mkdtemp()
687 self._cwd = os.getcwd()
688 os.chdir(self._resultsroot)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700689
Jakob Juelich24f22c22014-09-26 11:46:11 -0700690
J. Richard Barnetteea785362014-03-17 16:00:53 -0700691 def tearDown(self):
J. Richard Barnette08800322014-05-16 14:49:46 -0700692 os.chdir(self._cwd)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700693 shutil.rmtree(self._resultsroot)
694 super(_TempResultsDirTestBase, self).tearDown()
695
Jakob Juelich24f22c22014-09-26 11:46:11 -0700696
J. Richard Barnetteea785362014-03-17 16:00:53 -0700697 def make_job(self, jobdir):
698 """Create a job with results in `self._resultsroot`.
699
700 @param jobdir Name of the subdirectory to be created in
701 `self._resultsroot`.
702
703 """
J. Richard Barnette08800322014-05-16 14:49:46 -0700704 os.mkdir(jobdir)
705 return _MockJobDirectory(jobdir)
706
Jakob Juelich24f22c22014-09-26 11:46:11 -0700707
J. Richard Barnette08800322014-05-16 14:49:46 -0700708 def make_job_hierarchy(self):
709 """Create a sample hierarchy of job directories.
710
711 `self.REGULAR_JOBLIST` is a list of directories for regular
712 jobs to be created; `self.SPECIAL_JOBLIST` is a list of
713 directories for special jobs to be created.
714
715 """
716 for d in self.REGULAR_JOBLIST:
717 os.mkdir(d)
718 hostsdir = 'hosts'
719 os.mkdir(hostsdir)
720 for host in self.HOST_LIST:
721 os.mkdir(os.path.join(hostsdir, host))
722 for d in self.SPECIAL_JOBLIST:
723 os.mkdir(d)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700724
725
Prathmesh Prabhu80dfb1e2017-01-30 18:01:29 -0800726class FailedOffloadsLogTest(_TempResultsDirTestBase):
727 """Test the formatting of failed offloads log file."""
728 # Below is partial sample of a failed offload log file. This text is
729 # deliberately hard-coded and then parsed to create the test data; the idea
730 # is to make sure the actual text format will be reviewed by a human being.
731 #
732 # first offload count directory
733 # --+----1----+---- ----+ ----+----1----+----2----+----3
734 _SAMPLE_DIRECTORIES_REPORT = '''\
735 =================== ====== ==============================
736 2014-03-14 15:09:26 1 118-fubar
737 2014-03-14 15:19:23 2 117-fubar
738 2014-03-14 15:29:20 6 116-fubar
739 2014-03-14 15:39:17 24 115-fubar
740 2014-03-14 15:49:14 120 114-fubar
741 2014-03-14 15:59:11 720 113-fubar
742 2014-03-14 16:09:08 5040 112-fubar
743 2014-03-14 16:19:05 40320 111-fubar
744 '''
745
746 def setUp(self):
747 super(FailedOffloadsLogTest, self).setUp()
748 self._offloader = gs_offloader.Offloader(_get_options([]))
749 self._joblist = []
750 for line in self._SAMPLE_DIRECTORIES_REPORT.split('\n')[1 : -1]:
751 date_, time_, count, dir_ = line.split()
752 job = _MockJobDirectory(dir_)
753 job._offload_count = int(count)
754 timestruct = time.strptime("%s %s" % (date_, time_),
755 gs_offloader.FAILED_OFFLOADS_TIME_FORMAT)
756 job._first_offload_start = time.mktime(timestruct)
757 # enter the jobs in reverse order, to make sure we
758 # test that the output will be sorted.
759 self._joblist.insert(0, job)
760
761
762 def assert_report_well_formatted(self, report_file):
Allen Li93585382017-05-05 14:24:53 -0700763 """Assert that report file is well formatted.
764
765 @param report_file: Path to report file
766 """
Prathmesh Prabhu80dfb1e2017-01-30 18:01:29 -0800767 with open(report_file, 'r') as f:
768 report_lines = f.read().split()
769
770 for end_of_header_index in range(len(report_lines)):
771 if report_lines[end_of_header_index].startswith('=='):
772 break
773 self.assertLess(end_of_header_index, len(report_lines),
774 'Failed to find end-of-header marker in the report')
775
776 relevant_lines = report_lines[end_of_header_index:]
777 expected_lines = self._SAMPLE_DIRECTORIES_REPORT.split()
778 self.assertListEqual(relevant_lines, expected_lines)
779
780
781 def test_failed_offload_log_format(self):
782 """Trigger an e-mail report and check its contents."""
783 log_file = os.path.join(self._resultsroot, 'failed_log')
784 report = self._offloader._log_failed_jobs_locally(self._joblist,
785 log_file=log_file)
786 self.assert_report_well_formatted(log_file)
787
788
789 def test_failed_offload_file_overwrite(self):
790 """Verify that we can saefly overwrite the log file."""
791 log_file = os.path.join(self._resultsroot, 'failed_log')
792 with open(log_file, 'w') as f:
793 f.write('boohoohoo')
794 report = self._offloader._log_failed_jobs_locally(self._joblist,
795 log_file=log_file)
796 self.assert_report_well_formatted(log_file)
797
798
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700799class OffloadDirectoryTests(_TempResultsDirTestBase):
800 """Tests for `offload_dir()`."""
801
802 def setUp(self):
803 super(OffloadDirectoryTests, self).setUp()
804 # offload_dir() logs messages; silence them.
805 self._saved_loglevel = logging.getLogger().getEffectiveLevel()
806 logging.getLogger().setLevel(logging.CRITICAL+1)
807 self._job = self.make_job(self.REGULAR_JOBLIST[0])
808 self.mox.StubOutWithMock(gs_offloader, 'get_cmd_list')
809 self.mox.StubOutWithMock(signal, 'alarm')
Ningning Xia2d981ee2016-07-06 17:59:54 -0700810 self.mox.StubOutWithMock(models.test, 'parse_job_keyval')
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700811
Jakob Juelich24f22c22014-09-26 11:46:11 -0700812
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700813 def tearDown(self):
814 logging.getLogger().setLevel(self._saved_loglevel)
815 super(OffloadDirectoryTests, self).tearDown()
816
Ningning Xia42111242016-06-15 14:35:58 -0700817 def _mock_upload_testresult_files(self):
818 self.mox.StubOutWithMock(gs_offloader, 'upload_testresult_files')
819 gs_offloader.upload_testresult_files(
820 mox.IgnoreArg(),mox.IgnoreArg()).AndReturn(None)
Jakob Juelich24f22c22014-09-26 11:46:11 -0700821
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800822 def _mock_create_marker_file(self):
823 self.mox.StubOutWithMock(__builtin__, 'open')
Allen Li9579b382017-05-05 17:07:43 -0700824 open(mox.IgnoreArg(), 'a').AndReturn(mock.MagicMock())
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800825
826
827 def _mock_offload_dir_calls(self, command, queue_args,
828 marker_initially_exists=False,
829 marker_eventually_exists=True):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700830 """Mock out the calls needed by `offload_dir()`.
831
832 This covers only the calls made when there is no timeout.
833
834 @param command Command list to be returned by the mocked
835 call to `get_cmd_list()`.
836
837 """
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800838 self.mox.StubOutWithMock(os.path, 'isfile')
839 os.path.isfile(mox.IgnoreArg()).AndReturn(marker_initially_exists)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700840 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
Simran Basidd129972014-09-11 14:34:49 -0700841 command.append(queue_args[0])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700842 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700843 False, queue_args[0],
844 '%s%s' % (utils.DEFAULT_OFFLOAD_GSURI,
845 queue_args[1])).AndReturn(command)
Ningning Xia42111242016-06-15 14:35:58 -0700846 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700847 signal.alarm(0)
848 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800849 os.path.isfile(mox.IgnoreArg()).AndReturn(marker_eventually_exists)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700850
Jakob Juelich24f22c22014-09-26 11:46:11 -0700851
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800852 def _run_offload_dir(self, should_succeed, delete_age):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700853 """Make one call to `offload_dir()`.
854
855 The caller ensures all mocks are set up already.
856
857 @param should_succeed True iff the call to `offload_dir()`
858 is expected to succeed and remove the
859 offloaded job directory.
860
861 """
862 self.mox.ReplayAll()
Simran Basidd129972014-09-11 14:34:49 -0700863 gs_offloader.get_offload_dir_func(
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800864 utils.DEFAULT_OFFLOAD_GSURI, False, delete_age)(
MK Ryue93c8572015-08-11 11:53:00 -0700865 self._job.queue_args[0],
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800866 self._job.queue_args[1],
867 self._job.queue_args[2])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700868 self.mox.VerifyAll()
869 self.assertEqual(not should_succeed,
870 os.path.isdir(self._job.queue_args[0]))
871
Jakob Juelich24f22c22014-09-26 11:46:11 -0700872
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700873 def test_offload_success(self):
874 """Test that `offload_dir()` can succeed correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700875 self._mock_offload_dir_calls(['test', '-d'],
876 self._job.queue_args)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800877 self._mock_create_marker_file()
878 self._run_offload_dir(True, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700879
Jakob Juelich24f22c22014-09-26 11:46:11 -0700880
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700881 def test_offload_failure(self):
882 """Test that `offload_dir()` can fail correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700883 self._mock_offload_dir_calls(['test', '!', '-d'],
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800884 self._job.queue_args,
885 marker_eventually_exists=False)
886 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700887
Jakob Juelich24f22c22014-09-26 11:46:11 -0700888
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700889 def test_offload_timeout_early(self):
890 """Test that `offload_dir()` times out correctly.
891
892 This test triggers timeout at the earliest possible moment,
893 at the first call to set the timeout alarm.
894
895 """
Ningning Xia42111242016-06-15 14:35:58 -0700896 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700897 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS).AndRaise(
898 gs_offloader.TimeoutException('fubar'))
899 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800900 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700901
Jakob Juelich24f22c22014-09-26 11:46:11 -0700902
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700903 def test_offload_timeout_late(self):
904 """Test that `offload_dir()` times out correctly.
905
906 This test triggers timeout at the latest possible moment, at
907 the call to clear the timeout alarm.
908
909 """
910 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
911 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700912 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700913 ['test', '-d', self._job.queue_args[0]])
Ningning Xia42111242016-06-15 14:35:58 -0700914 self._mock_upload_testresult_files()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700915 signal.alarm(0).AndRaise(
916 gs_offloader.TimeoutException('fubar'))
917 signal.alarm(0)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800918 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700919
920
Dan Shiaffb9222015-04-15 17:05:47 -0700921 def test_sanitize_dir(self):
922 """Test that folder/file name with invalid character can be corrected.
923 """
924 results_folder = tempfile.mkdtemp()
925 invalid_chars = '_'.join(gs_offloader.INVALID_GS_CHARS)
926 invalid_files = []
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800927 invalid_folder_name = 'invalid_name_folder_%s' % invalid_chars
Dan Shiaffb9222015-04-15 17:05:47 -0700928 invalid_folder = os.path.join(
929 results_folder,
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800930 invalid_folder_name)
Dan Shiaffb9222015-04-15 17:05:47 -0700931 invalid_files.append(os.path.join(
932 invalid_folder,
933 'invalid_name_file_%s' % invalid_chars))
934 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
935 for c in range(r[0], r[1]+1):
936 # NULL cannot be in file name.
937 if c != 0:
938 invalid_files.append(os.path.join(
939 invalid_folder,
940 'invalid_name_file_%s' % chr(c)))
941 good_folder = os.path.join(results_folder, 'valid_name_folder')
942 good_file = os.path.join(good_folder, 'valid_name_file')
943 for folder in [invalid_folder, good_folder]:
944 os.makedirs(folder)
945 for f in invalid_files + [good_file]:
946 with open(f, 'w'):
947 pass
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800948 # check that broken symlinks don't break sanitization
949 symlink = os.path.join(invalid_folder, 'broken-link')
950 os.symlink(os.path.join(results_folder, 'no-such-file'),
951 symlink)
952 fifo1 = os.path.join(results_folder, 'test_fifo1')
953 fifo2 = os.path.join(good_folder, 'test_fifo2')
954 fifo3 = os.path.join(invalid_folder, 'test_fifo3')
955 invalid_fifo4_name = 'test_fifo4_%s' % invalid_chars
956 fifo4 = os.path.join(invalid_folder, invalid_fifo4_name)
957 os.mkfifo(fifo1)
958 os.mkfifo(fifo2)
959 os.mkfifo(fifo3)
960 os.mkfifo(fifo4)
Dan Shiaffb9222015-04-15 17:05:47 -0700961 gs_offloader.sanitize_dir(results_folder)
962 for _, dirs, files in os.walk(results_folder):
963 for name in dirs + files:
964 self.assertEqual(name, gs_offloader.get_sanitized_name(name))
965 for c in name:
966 self.assertFalse(c in gs_offloader.INVALID_GS_CHARS)
967 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
968 self.assertFalse(ord(c) >= r[0] and ord(c) <= r[1])
969 self.assertTrue(os.path.exists(good_file))
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800970
971 self.assertTrue(os.path.exists(fifo1))
972 self.assertFalse(is_fifo(fifo1))
973 self.assertTrue(os.path.exists(fifo2))
974 self.assertFalse(is_fifo(fifo2))
975 corrected_folder = os.path.join(
976 results_folder,
977 gs_offloader.get_sanitized_name(invalid_folder_name))
978 corrected_fifo3 = os.path.join(
979 corrected_folder,
980 'test_fifo3')
981 self.assertFalse(os.path.exists(fifo3))
982 self.assertTrue(os.path.exists(corrected_fifo3))
983 self.assertFalse(is_fifo(corrected_fifo3))
984 corrected_fifo4 = os.path.join(
985 corrected_folder,
986 gs_offloader.get_sanitized_name(invalid_fifo4_name))
987 self.assertFalse(os.path.exists(fifo4))
988 self.assertTrue(os.path.exists(corrected_fifo4))
989 self.assertFalse(is_fifo(corrected_fifo4))
990
991 corrected_symlink = os.path.join(
992 corrected_folder,
993 'broken-link')
994 self.assertFalse(os.path.lexists(symlink))
995 self.assertTrue(os.path.exists(corrected_symlink))
996 self.assertFalse(os.path.islink(corrected_symlink))
Dan Shiaffb9222015-04-15 17:05:47 -0700997 shutil.rmtree(results_folder)
998
999
Dan Shi1b4c7c32015-10-05 10:38:57 -07001000 def check_limit_file_count(self, is_test_job=True):
1001 """Test that folder with too many files can be compressed.
1002
1003 @param is_test_job: True to check the method with test job result
1004 folder. Set to False for special task folder.
1005 """
1006 results_folder = tempfile.mkdtemp()
1007 host_folder = os.path.join(
1008 results_folder,
1009 'lab1-host1' if is_test_job else 'hosts/lab1-host1/1-repair')
1010 debug_folder = os.path.join(host_folder, 'debug')
1011 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
1012 for folder in [debug_folder, sysinfo_folder]:
1013 os.makedirs(folder)
1014 for i in range(10):
1015 with open(os.path.join(folder, str(i)), 'w') as f:
1016 f.write('test')
1017
1018 gs_offloader.MAX_FILE_COUNT = 100
1019 gs_offloader.limit_file_count(
1020 results_folder if is_test_job else host_folder)
1021 self.assertTrue(os.path.exists(sysinfo_folder))
1022
1023 gs_offloader.MAX_FILE_COUNT = 10
1024 gs_offloader.limit_file_count(
1025 results_folder if is_test_job else host_folder)
1026 self.assertFalse(os.path.exists(sysinfo_folder))
1027 self.assertTrue(os.path.exists(sysinfo_folder + '.tgz'))
1028 self.assertTrue(os.path.exists(debug_folder))
1029
1030 shutil.rmtree(results_folder)
1031
1032
1033 def test_limit_file_count(self):
1034 """Test that folder with too many files can be compressed.
1035 """
1036 self.check_limit_file_count(is_test_job=True)
1037 self.check_limit_file_count(is_test_job=False)
1038
Ningning Xia2d88eec2016-07-25 23:18:46 -07001039
Ningning Xia8db632f2016-08-19 11:01:35 -07001040 def test_is_valid_result(self):
1041 """Test _is_valid_result."""
Ningning Xia21922c82016-07-29 11:03:15 -07001042 release_build = 'veyron_minnie-cheets-release/R52-8248.0.0'
1043 pfq_build = 'cyan-cheets-android-pfq/R54-8623.0.0-rc1'
1044 trybot_build = 'trybot-samus-release/R54-8640.0.0-b5092'
1045 trybot_2_build = 'trybot-samus-pfq/R54-8640.0.0-b5092'
1046 release_2_build = 'test-trybot-release/R54-8640.0.0-b5092'
Ningning Xia8db632f2016-08-19 11:01:35 -07001047 self.assertTrue(gs_offloader._is_valid_result(
1048 release_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Rohit Makasana6384c102016-10-21 17:09:47 -07001049 self.assertTrue(gs_offloader._is_valid_result(
1050 release_build, gs_offloader.CTS_RESULT_PATTERN, 'test_that_wrapper'))
Ningning Xia8db632f2016-08-19 11:01:35 -07001051 self.assertFalse(gs_offloader._is_valid_result(
1052 release_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-bvt-cq'))
1053 self.assertTrue(gs_offloader._is_valid_result(
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001054 release_build, gs_offloader.CTS_V2_RESULT_PATTERN, 'arc-gts'))
Ningning Xia8db632f2016-08-19 11:01:35 -07001055 self.assertFalse(gs_offloader._is_valid_result(
1056 None, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1057 self.assertFalse(gs_offloader._is_valid_result(
1058 release_build, gs_offloader.CTS_RESULT_PATTERN, None))
1059 self.assertFalse(gs_offloader._is_valid_result(
1060 pfq_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1061 self.assertFalse(gs_offloader._is_valid_result(
1062 trybot_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1063 self.assertFalse(gs_offloader._is_valid_result(
1064 trybot_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
1065 self.assertTrue(gs_offloader._is_valid_result(
1066 release_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Ningning Xia21922c82016-07-29 11:03:15 -07001067
1068
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001069 def create_results_folder(self):
1070 """Create CTS/GTS results folders."""
Ningning Xia42111242016-06-15 14:35:58 -07001071 results_folder = tempfile.mkdtemp()
1072 host_folder = os.path.join(results_folder, 'chromeos4-row9-rack11-host22')
1073 debug_folder = os.path.join(host_folder, 'debug')
1074 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
1075 cts_result_folder = os.path.join(
1076 host_folder, 'cheets_CTS.android.dpi', 'results', 'cts-results')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001077 cts_v2_result_folder = os.path.join(host_folder,
1078 'cheets_CTS_N.CtsGraphicsTestCases', 'results', 'android-cts')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001079 gts_result_folder = os.path.join(
Ilja H. Friedelbfa63142017-01-26 00:56:29 -08001080 host_folder, 'cheets_GTS.google.admin', 'results', 'android-gts')
Ningning Xia42111242016-06-15 14:35:58 -07001081 timestamp_str = '2016.04.28_01.41.44'
Ningning Xia2d88eec2016-07-25 23:18:46 -07001082 timestamp_cts_folder = os.path.join(cts_result_folder, timestamp_str)
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001083 timestamp_cts_v2_folder = os.path.join(cts_v2_result_folder, timestamp_str)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001084 timestamp_gts_folder = os.path.join(gts_result_folder, timestamp_str)
1085
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001086 # Test results in cts_result_folder with a different time-stamp.
1087 timestamp_str_2 = '2016.04.28_10.41.44'
1088 timestamp_cts_folder_2 = os.path.join(cts_result_folder, timestamp_str_2)
1089
Ningning Xia2d88eec2016-07-25 23:18:46 -07001090 for folder in [debug_folder, sysinfo_folder, cts_result_folder,
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001091 timestamp_cts_folder, timestamp_cts_folder_2,
1092 timestamp_cts_v2_folder, timestamp_gts_folder]:
Ningning Xia42111242016-06-15 14:35:58 -07001093 os.makedirs(folder)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001094
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001095 path_pattern_pair = [(timestamp_cts_folder, gs_offloader.CTS_RESULT_PATTERN),
1096 (timestamp_cts_folder_2, gs_offloader.CTS_RESULT_PATTERN),
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001097 (timestamp_cts_v2_folder, gs_offloader.CTS_V2_RESULT_PATTERN),
1098 (timestamp_gts_folder, gs_offloader.CTS_V2_RESULT_PATTERN)]
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001099
1100 # Create timestamp.zip file_path.
Ningning Xia2d88eec2016-07-25 23:18:46 -07001101 cts_zip_file = os.path.join(cts_result_folder, timestamp_str + '.zip')
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001102 cts_zip_file_2 = os.path.join(cts_result_folder, timestamp_str_2 + '.zip')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001103 cts_v2_zip_file = os.path.join(cts_v2_result_folder, timestamp_str + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001104 gts_zip_file = os.path.join(gts_result_folder, timestamp_str + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001105
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001106 # Create xml file_path.
1107 cts_result_file = os.path.join(timestamp_cts_folder, 'testResult.xml')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001108 cts_result_file_2 = os.path.join(timestamp_cts_folder_2,
1109 'testResult.xml')
Ilja H. Friedelbfa63142017-01-26 00:56:29 -08001110 gts_result_file = os.path.join(timestamp_gts_folder, 'test_result.xml')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001111 cts_v2_result_file = os.path.join(timestamp_cts_v2_folder,
1112 'test_result.xml')
Ningning Xia2d88eec2016-07-25 23:18:46 -07001113
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -08001114 for file_path in [cts_zip_file, cts_zip_file_2, cts_v2_zip_file,
1115 gts_zip_file, cts_result_file, cts_result_file_2,
1116 gts_result_file, cts_v2_result_file]:
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001117 with open(file_path, 'w') as f:
1118 f.write('test')
Ningning Xia42111242016-06-15 14:35:58 -07001119
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001120 return (results_folder, host_folder, path_pattern_pair)
Ningning Xia2d88eec2016-07-25 23:18:46 -07001121
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001122
1123 def test_upload_testresult_files(self):
1124 """Test upload_testresult_files."""
1125 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
1126
1127 self.mox.StubOutWithMock(gs_offloader, '_upload_files')
1128 gs_offloader._upload_files(
1129 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1130 ['test', '-d', host_folder])
1131 gs_offloader._upload_files(
1132 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1133 ['test', '-d', host_folder])
1134 gs_offloader._upload_files(
1135 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False).AndReturn(
1136 ['test', '-d', host_folder])
Ningning Xia2d88eec2016-07-25 23:18:46 -07001137
Ningning Xia42111242016-06-15 14:35:58 -07001138 self.mox.ReplayAll()
1139 gs_offloader.upload_testresult_files(results_folder, False)
1140 self.mox.VerifyAll()
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001141 shutil.rmtree(results_folder)
1142
1143
1144 def test_upload_files(self):
1145 """Test upload_files"""
1146 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
1147
1148 for path, pattern in path_pattern_pair:
1149 models.test.parse_job_keyval(mox.IgnoreArg()).AndReturn({
1150 'build': 'veyron_minnie-cheets-release/R52-8248.0.0',
Ningning Xia8db632f2016-08-19 11:01:35 -07001151 'parent_job_id': 'p_id',
1152 'suite': 'arc-cts'
Ningning Xia0c27d9b2016-08-04 14:02:39 -07001153 })
1154
1155 gs_offloader.get_cmd_list(
1156 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
1157 ['test', '-d', path])
1158 gs_offloader.get_cmd_list(
1159 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
1160 ['test', '-d', path])
1161
1162 self.mox.ReplayAll()
1163 gs_offloader._upload_files(host_folder, path, pattern, False)
1164 self.mox.VerifyAll()
1165 self.mox.ResetAll()
Ningning Xia42111242016-06-15 14:35:58 -07001166
1167 shutil.rmtree(results_folder)
Dan Shi1b4c7c32015-10-05 10:38:57 -07001168
Michael Tang97d188c2016-06-25 11:18:42 -07001169
Dan Shi02dd0662017-05-23 11:24:32 -07001170 def test_get_metrics_fields(self):
1171 """Test method _get_metrics_fields."""
1172 results_folder, host_folder, _ = self.create_results_folder()
1173 models.test.parse_job_keyval(mox.IgnoreArg()).AndReturn({
1174 'build': 'veyron_minnie-cheets-release/R52-8248.0.0',
1175 'parent_job_id': 'p_id',
1176 'suite': 'arc-cts'
1177 })
1178 try:
1179 self.mox.ReplayAll()
1180 self.assertEqual({'board': 'veyron_minnie-cheets',
1181 'milestone': 'R52'},
1182 gs_offloader._get_metrics_fields(host_folder))
1183 self.mox.VerifyAll()
1184 finally:
1185 shutil.rmtree(results_folder)
1186
1187
J. Richard Barnetteea785362014-03-17 16:00:53 -07001188class JobDirectoryOffloadTests(_TempResultsDirTestBase):
1189 """Tests for `_JobDirectory.enqueue_offload()`.
1190
1191 When testing with a `days_old` parameter of 0, we use
1192 `set_finished()` instead of `set_expired()`. This causes the
1193 job's timestamp to be set in the future. This is done so as
1194 to test that when `days_old` is 0, the job is always treated
1195 as eligible for offload, regardless of the timestamp's value.
1196
1197 Testing covers the following assertions:
1198 A. Each time `enqueue_offload()` is called, a message that
1199 includes the job's directory name will be logged using
1200 `logging.debug()`, regardless of whether the job was
1201 enqueued. Nothing else is allowed to be logged.
1202 B. If the job is not eligible to be offloaded,
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001203 `get_failure_time()` and `get_failure_count()` are 0.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001204 C. If the job is not eligible for offload, nothing is
1205 enqueued in `queue`.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001206 D. When the job is offloaded, `get_failure_count()` increments
J. Richard Barnetteea785362014-03-17 16:00:53 -07001207 each time.
1208 E. When the job is offloaded, the appropriate parameters are
1209 enqueued exactly once.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001210 F. The first time a job is offloaded, `get_failure_time()` is
J. Richard Barnetteea785362014-03-17 16:00:53 -07001211 set to the current time.
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001212 G. `get_failure_time()` only changes the first time that the
J. Richard Barnetteea785362014-03-17 16:00:53 -07001213 job is offloaded.
1214
1215 The test cases below are designed to exercise all of the
1216 meaningful state transitions at least once.
1217
1218 """
1219
1220 def setUp(self):
1221 super(JobDirectoryOffloadTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001222 self._job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001223 self._queue = Queue.Queue()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001224
Jakob Juelich24f22c22014-09-26 11:46:11 -07001225
J. Richard Barnetteea785362014-03-17 16:00:53 -07001226 def _offload_unexpired_job(self, days_old):
1227 """Make calls to `enqueue_offload()` for an unexpired job.
1228
1229 This method tests assertions B and C that calling
1230 `enqueue_offload()` has no effect.
1231
1232 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001233 self.assertEqual(self._job.get_failure_count(), 0)
1234 self.assertEqual(self._job.get_failure_time(), 0)
1235 self._job.enqueue_offload(self._queue, days_old)
1236 self._job.enqueue_offload(self._queue, days_old)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001237 self.assertTrue(self._queue.empty())
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001238 self.assertEqual(self._job.get_failure_count(), 0)
1239 self.assertEqual(self._job.get_failure_time(), 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001240
Jakob Juelich24f22c22014-09-26 11:46:11 -07001241
J. Richard Barnetteea785362014-03-17 16:00:53 -07001242 def _offload_expired_once(self, days_old, count):
1243 """Make one call to `enqueue_offload()` for an expired job.
1244
1245 This method tests assertions D and E regarding side-effects
1246 expected when a job is offloaded.
1247
1248 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001249 self._job.enqueue_offload(self._queue, days_old)
1250 self.assertEqual(self._job.get_failure_count(), count)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001251 self.assertFalse(self._queue.empty())
1252 v = self._queue.get_nowait()
1253 self.assertTrue(self._queue.empty())
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -07001254 self.assertEqual(v, self._job.queue_args)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001255
Jakob Juelich24f22c22014-09-26 11:46:11 -07001256
J. Richard Barnetteea785362014-03-17 16:00:53 -07001257 def _offload_expired_job(self, days_old):
1258 """Make calls to `enqueue_offload()` for a just-expired job.
1259
1260 This method directly tests assertions F and G regarding
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001261 side-effects on `get_failure_time()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001262
1263 """
1264 t0 = time.time()
1265 self._offload_expired_once(days_old, 1)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001266 t1 = self._job.get_failure_time()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001267 self.assertLessEqual(t1, time.time())
1268 self.assertGreaterEqual(t1, t0)
1269 self._offload_expired_once(days_old, 2)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001270 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001271 self._offload_expired_once(days_old, 3)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001272 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001273
Jakob Juelich24f22c22014-09-26 11:46:11 -07001274
J. Richard Barnetteea785362014-03-17 16:00:53 -07001275 def test_case_1_no_expiration(self):
1276 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1277
1278 This tests that offload works as expected if calls are
1279 made both before and after the job becomes expired.
1280
1281 """
1282 self._offload_unexpired_job(0)
1283 self._job.set_finished(0)
1284 self._offload_expired_job(0)
1285
Jakob Juelich24f22c22014-09-26 11:46:11 -07001286
J. Richard Barnetteea785362014-03-17 16:00:53 -07001287 def test_case_2_no_expiration(self):
1288 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1289
1290 This tests that offload works as expected if calls are made
1291 only after the job becomes expired.
1292
1293 """
1294 self._job.set_finished(0)
1295 self._offload_expired_job(0)
1296
Jakob Juelich24f22c22014-09-26 11:46:11 -07001297
J. Richard Barnetteea785362014-03-17 16:00:53 -07001298 def test_case_1_with_expiration(self):
1299 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1300
1301 This tests that offload works as expected if calls are made
1302 before the job finishes, before the job expires, and after
1303 the job expires.
1304
1305 """
1306 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1307 self._job.set_finished(_TEST_EXPIRATION_AGE)
1308 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1309 self._job.set_expired(_TEST_EXPIRATION_AGE)
1310 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1311
Jakob Juelich24f22c22014-09-26 11:46:11 -07001312
J. Richard Barnetteea785362014-03-17 16:00:53 -07001313 def test_case_2_with_expiration(self):
1314 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1315
1316 This tests that offload works as expected if calls are made
1317 between finishing and expiration, and after the job expires.
1318
1319 """
1320 self._job.set_finished(_TEST_EXPIRATION_AGE)
1321 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1322 self._job.set_expired(_TEST_EXPIRATION_AGE)
1323 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1324
Jakob Juelich24f22c22014-09-26 11:46:11 -07001325
J. Richard Barnetteea785362014-03-17 16:00:53 -07001326 def test_case_3_with_expiration(self):
1327 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1328
1329 This tests that offload works as expected if calls are made
1330 only before finishing and after expiration.
1331
1332 """
1333 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1334 self._job.set_expired(_TEST_EXPIRATION_AGE)
1335 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1336
Jakob Juelich24f22c22014-09-26 11:46:11 -07001337
J. Richard Barnetteea785362014-03-17 16:00:53 -07001338 def test_case_4_with_expiration(self):
1339 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1340
1341 This tests that offload works as expected if calls are made
1342 only after expiration.
1343
1344 """
1345 self._job.set_expired(_TEST_EXPIRATION_AGE)
1346 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1347
1348
1349class GetJobDirectoriesTests(_TempResultsDirTestBase):
1350 """Tests for `_JobDirectory.get_job_directories()`."""
1351
J. Richard Barnetteea785362014-03-17 16:00:53 -07001352 def setUp(self):
1353 super(GetJobDirectoriesTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001354 self.make_job_hierarchy()
1355 os.mkdir('not-a-job')
1356 open('not-a-dir', 'w').close()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001357
Jakob Juelich24f22c22014-09-26 11:46:11 -07001358
J. Richard Barnetteea785362014-03-17 16:00:53 -07001359 def _run_get_directories(self, cls, expected_list):
1360 """Test `get_job_directories()` for the given class.
1361
1362 Calls the method, and asserts that the returned list of
1363 directories matches the expected return value.
1364
1365 @param expected_list Expected return value from the call.
1366 """
J. Richard Barnetteea785362014-03-17 16:00:53 -07001367 dirlist = cls.get_job_directories()
1368 self.assertEqual(set(dirlist), set(expected_list))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001369
Jakob Juelich24f22c22014-09-26 11:46:11 -07001370
J. Richard Barnetteea785362014-03-17 16:00:53 -07001371 def test_get_regular_jobs(self):
1372 """Test `RegularJobDirectory.get_job_directories()`."""
1373 self._run_get_directories(job_directories.RegularJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001374 self.REGULAR_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001375
Jakob Juelich24f22c22014-09-26 11:46:11 -07001376
J. Richard Barnetteea785362014-03-17 16:00:53 -07001377 def test_get_special_jobs(self):
1378 """Test `SpecialJobDirectory.get_job_directories()`."""
1379 self._run_get_directories(job_directories.SpecialJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001380 self.SPECIAL_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001381
1382
1383class AddJobsTests(_TempResultsDirTestBase):
1384 """Tests for `Offloader._add_new_jobs()`."""
1385
J. Richard Barnette08800322014-05-16 14:49:46 -07001386 MOREJOBS = ['115-fubar', '116-fubar', '117-fubar', '118-snafu']
J. Richard Barnetteea785362014-03-17 16:00:53 -07001387
1388 def setUp(self):
1389 super(AddJobsTests, self).setUp()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001390 self._initial_job_names = (
1391 set(self.REGULAR_JOBLIST) | set(self.SPECIAL_JOBLIST))
J. Richard Barnette08800322014-05-16 14:49:46 -07001392 self.make_job_hierarchy()
1393 self._offloader = gs_offloader.Offloader(_get_options(['-a']))
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001394 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001395
Jakob Juelich24f22c22014-09-26 11:46:11 -07001396
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001397 def _run_add_new_jobs(self, expected_key_set):
J. Richard Barnetteea785362014-03-17 16:00:53 -07001398 """Basic test assertions for `_add_new_jobs()`.
1399
1400 Asserts the following:
1401 * The keys in the offloader's `_open_jobs` dictionary
1402 matches the expected set of keys.
1403 * For every job in `_open_jobs`, the job has the expected
1404 directory name.
1405
1406 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001407 count = len(expected_key_set) - len(self._offloader._open_jobs)
1408 logging.debug(mox.IgnoreArg(), count)
1409 self.mox.ReplayAll()
1410 self._offloader._add_new_jobs()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001411 self.assertEqual(expected_key_set,
1412 set(self._offloader._open_jobs.keys()))
1413 for jobkey, job in self._offloader._open_jobs.items():
1414 self.assertEqual(jobkey, job._dirname)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001415 self.mox.VerifyAll()
1416 self.mox.ResetAll()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001417
Jakob Juelich24f22c22014-09-26 11:46:11 -07001418
J. Richard Barnetteea785362014-03-17 16:00:53 -07001419 def test_add_jobs_empty(self):
1420 """Test adding jobs to an empty dictionary.
1421
1422 Calls the offloader's `_add_new_jobs()`, then perform
1423 the assertions of `self._check_open_jobs()`.
1424
1425 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001426 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001427
Jakob Juelich24f22c22014-09-26 11:46:11 -07001428
J. Richard Barnetteea785362014-03-17 16:00:53 -07001429 def test_add_jobs_non_empty(self):
1430 """Test adding jobs to a non-empty dictionary.
1431
1432 Calls the offloader's `_add_new_jobs()` twice; once from
1433 initial conditions, and then again after adding more
1434 directories. After the second call, perform the assertions
1435 of `self._check_open_jobs()`. Additionally, assert that
1436 keys added by the first call still map to their original
1437 job object after the second call.
1438
1439 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001440 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001441 jobs_copy = self._offloader._open_jobs.copy()
J. Richard Barnette08800322014-05-16 14:49:46 -07001442 for d in self.MOREJOBS:
1443 os.mkdir(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001444 self._run_add_new_jobs(self._initial_job_names |
1445 set(self.MOREJOBS))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001446 for key in jobs_copy.keys():
1447 self.assertIs(jobs_copy[key],
1448 self._offloader._open_jobs[key])
1449
1450
1451class JobStateTests(_TempResultsDirTestBase):
1452 """Tests for job state predicates.
1453
1454 This tests for the expected results from the
Prathmesh Prabhuca481592017-01-30 18:05:49 -08001455 `is_offloaded()` predicate method.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001456
1457 """
1458
1459 def test_unfinished_job(self):
1460 """Test that an unfinished job reports the correct state.
1461
1462 A job is "unfinished" if it isn't marked complete in the
1463 database. A job in this state is neither "complete" nor
1464 "reportable".
1465
1466 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001467 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001468 self.assertFalse(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001469
Jakob Juelich24f22c22014-09-26 11:46:11 -07001470
J. Richard Barnetteea785362014-03-17 16:00:53 -07001471 def test_incomplete_job(self):
1472 """Test that an incomplete job reports the correct state.
1473
1474 A job is "incomplete" if exactly one attempt has been made
1475 to offload the job, but its results directory still exists.
1476 A job in this state is neither "complete" nor "reportable".
1477
1478 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001479 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001480 job.set_incomplete()
1481 self.assertFalse(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001482
Jakob Juelich24f22c22014-09-26 11:46:11 -07001483
J. Richard Barnetteea785362014-03-17 16:00:53 -07001484 def test_reportable_job(self):
1485 """Test that a reportable job reports the correct state.
1486
1487 A job is "reportable" if more than one attempt has been made
1488 to offload the job, and its results directory still exists.
1489 A job in this state is "reportable", but not "complete".
1490
1491 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001492 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001493 job.set_reportable()
1494 self.assertFalse(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001495
Jakob Juelich24f22c22014-09-26 11:46:11 -07001496
J. Richard Barnetteea785362014-03-17 16:00:53 -07001497 def test_completed_job(self):
1498 """Test that a completed job reports the correct state.
1499
1500 A job is "completed" if at least one attempt has been made
1501 to offload the job, and its results directory still exists.
1502 A job in this state is "complete", and not "reportable".
1503
1504 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001505 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001506 job.set_complete()
1507 self.assertTrue(job.is_offloaded())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001508
1509
1510class ReportingTests(_TempResultsDirTestBase):
1511 """Tests for `Offloader._update_offload_results()`."""
1512
J. Richard Barnetteea785362014-03-17 16:00:53 -07001513 def setUp(self):
1514 super(ReportingTests, self).setUp()
1515 self._offloader = gs_offloader.Offloader(_get_options([]))
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001516 self.mox.StubOutWithMock(self._offloader, '_log_failed_jobs_locally')
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001517 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001518
Jakob Juelich24f22c22014-09-26 11:46:11 -07001519
J. Richard Barnetteea785362014-03-17 16:00:53 -07001520 def _add_job(self, jobdir):
1521 """Add a job to the dictionary of unfinished jobs."""
1522 j = self.make_job(jobdir)
1523 self._offloader._open_jobs[j._dirname] = j
1524 return j
1525
Jakob Juelich24f22c22014-09-26 11:46:11 -07001526
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001527 def _expect_log_message(self, new_open_jobs, with_failures):
1528 """Mock expected logging calls.
1529
1530 `_update_offload_results()` logs one message with the number
1531 of jobs removed from the open job set and the number of jobs
1532 still remaining. Additionally, if there are reportable
1533 jobs, then it logs the number of jobs that haven't yet
1534 offloaded.
1535
1536 This sets up the logging calls using `new_open_jobs` to
1537 figure the job counts. If `with_failures` is true, then
1538 the log message is set up assuming that all jobs in
1539 `new_open_jobs` have offload failures.
1540
1541 @param new_open_jobs New job set for calculating counts
1542 in the messages.
1543 @param with_failures Whether the log message with a
1544 failure count is expected.
1545
1546 """
1547 count = len(self._offloader._open_jobs) - len(new_open_jobs)
1548 logging.debug(mox.IgnoreArg(), count, len(new_open_jobs))
1549 if with_failures:
1550 logging.debug(mox.IgnoreArg(), len(new_open_jobs))
1551
Jakob Juelich24f22c22014-09-26 11:46:11 -07001552
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001553 def _run_update(self, new_open_jobs):
1554 """Call `_update_offload_results()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001555
1556 Initial conditions are set up by the caller. This calls
1557 `_update_offload_results()` once, and then checks these
1558 assertions:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001559 * The offloader's new `_open_jobs` field contains only
1560 the entries in `new_open_jobs`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001561
1562 @param new_open_jobs A dictionary representing the expected
1563 new value of the offloader's
1564 `_open_jobs` field.
1565 """
1566 self.mox.ReplayAll()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001567 self._offloader._update_offload_results()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001568 self.assertEqual(self._offloader._open_jobs, new_open_jobs)
1569 self.mox.VerifyAll()
1570 self.mox.ResetAll()
1571
Jakob Juelich24f22c22014-09-26 11:46:11 -07001572
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001573 def _expect_failed_jobs(self, failed_jobs):
1574 """Mock expected call to log the failed jobs on local disk.
1575
1576 TODO(crbug.com/686904): The fact that we have to mock an internal
1577 function for this test is evidence that we need to pull out the local
1578 file formatter in its own object in a future CL.
1579
1580 @param failed_jobs: The list of jobs being logged as failed.
1581 """
1582 self._offloader._log_failed_jobs_locally(failed_jobs)
1583
1584
J. Richard Barnetteea785362014-03-17 16:00:53 -07001585 def test_no_jobs(self):
1586 """Test `_update_offload_results()` with no open jobs.
1587
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001588 Initial conditions are an empty `_open_jobs` list.
1589 Expected result is an empty `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001590
1591 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001592 self._expect_log_message({}, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001593 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001594 self._run_update({})
J. Richard Barnetteea785362014-03-17 16:00:53 -07001595
Jakob Juelich24f22c22014-09-26 11:46:11 -07001596
J. Richard Barnetteea785362014-03-17 16:00:53 -07001597 def test_all_completed(self):
1598 """Test `_update_offload_results()` with only complete jobs.
1599
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001600 Initial conditions are an `_open_jobs` list consisting of only completed
1601 jobs.
1602 Expected result is an empty `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001603
1604 """
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_complete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001607 self._expect_log_message({}, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001608 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001609 self._run_update({})
J. Richard Barnetteea785362014-03-17 16:00:53 -07001610
Jakob Juelich24f22c22014-09-26 11:46:11 -07001611
J. Richard Barnetteea785362014-03-17 16:00:53 -07001612 def test_none_finished(self):
1613 """Test `_update_offload_results()` with only unfinished jobs.
1614
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001615 Initial conditions are an `_open_jobs` list consisting of only
1616 unfinished jobs.
1617 Expected result is no change to the `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001618
1619 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001620 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001621 self._add_job(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001622 new_jobs = self._offloader._open_jobs.copy()
1623 self._expect_log_message(new_jobs, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001624 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001625 self._run_update(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001626
1627
1628if __name__ == '__main__':
1629 unittest.main()