Home | History | Annotate | Download | only in server
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import StringIO
      6 import json
      7 import mox
      8 import time
      9 import unittest
     10 import urllib2
     11 
     12 import common
     13 from autotest_lib.client.common_lib import global_config
     14 from autotest_lib.server import site_utils
     15 
     16 _DEADBUILD = 'deadboard-release/R33-4966.0.0'
     17 _LIVEBUILD = 'liveboard-release/R32-4920.14.0'
     18 
     19 _STATUS_TEMPLATE = '''
     20     {
     21       "username": "fizzbin (at] google.com",
     22       "date": "2013-11-16 00:25:23.511208",
     23       "message": "%s",
     24       "can_commit_freely": %s,
     25       "general_state": "%s"
     26     }
     27     '''
     28 
     29 
     30 def _make_status(message, can_commit, state):
     31     return _STATUS_TEMPLATE % (message, can_commit, state)
     32 
     33 
     34 def _make_open_status(message, state):
     35     return _make_status(message, 'true', state)
     36 
     37 
     38 def _make_closed_status(message):
     39     return _make_status(message, 'false', 'closed')
     40 
     41 
     42 def _make_deadbuild_status(message):
     43     return _make_status(message, 'false', 'open')
     44 
     45 
     46 _OPEN_STATUS_VALUES = [
     47     _make_open_status('Lab is up (cross your fingers)', 'open'),
     48     _make_open_status('Lab is on fire', 'throttled'),
     49     _make_open_status('Lab is up despite deadboard', 'open'),
     50     _make_open_status('Lab is up despite .*/R33-4966.0.0', 'open'),
     51 ]
     52 
     53 _CLOSED_STATUS_VALUES = [
     54     _make_closed_status('Lab is down for spite'),
     55     _make_closed_status('Lab is down even for [%s]' % _LIVEBUILD),
     56     _make_closed_status('Lab is down even for [%s]' % _DEADBUILD),
     57 ]
     58 
     59 _DEADBUILD_STATUS_VALUES = [
     60     _make_deadbuild_status('Lab is up except for [deadboard-]'),
     61     _make_deadbuild_status('Lab is up except for [board- deadboard-]'),
     62     _make_deadbuild_status('Lab is up except for [.*/R33-]'),
     63     _make_deadbuild_status('Lab is up except for [deadboard-.*/R33-]'),
     64     _make_deadbuild_status('Lab is up except for [ deadboard-]'),
     65     _make_deadbuild_status('Lab is up except for [deadboard- ]'),
     66     _make_deadbuild_status('Lab is up [first .*/R33- last]'),
     67     _make_deadbuild_status('liveboard is good, but [deadboard-] is bad'),
     68     _make_deadbuild_status('Lab is up [deadboard- otherboard-]'),
     69     _make_deadbuild_status('Lab is up [otherboard- deadboard-]'),
     70 ]
     71 
     72 
     73 _FAKE_URL = 'ignore://not.a.url'
     74 
     75 
     76 class _FakeURLResponse(object):
     77 
     78     """Everything needed to pretend to be a response from urlopen().
     79 
     80     Creates a StringIO instance to handle the File operations.
     81 
     82     N.B.  StringIO is lame:  we can't inherit from it (super won't
     83     work), and it doesn't implement __getattr__(), either.  So, we
     84     have to manually forward calls to the StringIO object.  This
     85     forwards only what empirical testing says is required; YMMV.
     86 
     87     """
     88 
     89     def __init__(self, code, buffer):
     90         self._stringio = StringIO.StringIO(buffer)
     91         self._code = code
     92 
     93 
     94     def read(self, size=-1):
     95         """Standard file-like read operation.
     96 
     97         @param size size for read operation.
     98         """
     99         return self._stringio.read(size)
    100 
    101 
    102     def getcode(self):
    103         """Get URL HTTP response code."""
    104         return self._code
    105 
    106 
    107 class GetStatusTest(mox.MoxTestBase):
    108 
    109     """Test case for _get_lab_status().
    110 
    111     We mock out dependencies on urllib2 and time.sleep(), and
    112     confirm that the function returns the proper JSON representation
    113     for a pre-defined response.
    114 
    115     """
    116 
    117     def setUp(self):
    118         super(GetStatusTest, self).setUp()
    119         self.mox.StubOutWithMock(urllib2, 'urlopen')
    120         self.mox.StubOutWithMock(time, 'sleep')
    121 
    122 
    123     def test_success(self):
    124         """Test that successful calls to urlopen() succeed."""
    125         json_string = _OPEN_STATUS_VALUES[0]
    126         json_value = json.loads(json_string)
    127         urllib2.urlopen(mox.IgnoreArg()).AndReturn(
    128                 _FakeURLResponse(200, json_string))
    129         self.mox.ReplayAll()
    130         result = site_utils._get_lab_status(_FAKE_URL)
    131         self.mox.VerifyAll()
    132         self.assertEqual(json_value, result)
    133 
    134 
    135     def test_retry_ioerror(self):
    136         """Test that an IOError retries at least once."""
    137         json_string = _OPEN_STATUS_VALUES[0]
    138         json_value = json.loads(json_string)
    139         urllib2.urlopen(mox.IgnoreArg()).AndRaise(
    140                 IOError('Fake I/O error for a fake URL'))
    141         time.sleep(mox.IgnoreArg()).AndReturn(None)
    142         urllib2.urlopen(mox.IgnoreArg()).AndReturn(
    143                 _FakeURLResponse(200, json_string))
    144         self.mox.ReplayAll()
    145         result = site_utils._get_lab_status(_FAKE_URL)
    146         self.mox.VerifyAll()
    147         self.assertEqual(json_value, result)
    148 
    149 
    150     def test_retry_http_internal_error(self):
    151         """Test that an HTTP error retries at least once."""
    152         json_string = _OPEN_STATUS_VALUES[0]
    153         json_value = json.loads(json_string)
    154         urllib2.urlopen(mox.IgnoreArg()).AndReturn(
    155                 _FakeURLResponse(500, ''))
    156         time.sleep(mox.IgnoreArg()).AndReturn(None)
    157         urllib2.urlopen(mox.IgnoreArg()).AndReturn(
    158                 _FakeURLResponse(200, json_string))
    159         self.mox.ReplayAll()
    160         result = site_utils._get_lab_status(_FAKE_URL)
    161         self.mox.VerifyAll()
    162         self.assertEqual(json_value, result)
    163 
    164 
    165     def test_failure_ioerror(self):
    166         """Test that there's a failure if urlopen() never succeeds."""
    167         json_string = _OPEN_STATUS_VALUES[0]
    168         json_value = json.loads(json_string)
    169         for _ in range(site_utils._MAX_LAB_STATUS_ATTEMPTS):
    170             urllib2.urlopen(mox.IgnoreArg()).AndRaise(
    171                     IOError('Fake I/O error for a fake URL'))
    172             time.sleep(mox.IgnoreArg()).AndReturn(None)
    173         self.mox.ReplayAll()
    174         result = site_utils._get_lab_status(_FAKE_URL)
    175         self.mox.VerifyAll()
    176         self.assertEqual(None, result)
    177 
    178 
    179     def test_failure_http_internal_error(self):
    180         """Test that there's a failure for a permanent HTTP error."""
    181         json_string = _OPEN_STATUS_VALUES[0]
    182         json_value = json.loads(json_string)
    183         for _ in range(site_utils._MAX_LAB_STATUS_ATTEMPTS):
    184             urllib2.urlopen(mox.IgnoreArg()).AndReturn(
    185                     _FakeURLResponse(404, 'Not here, never gonna be'))
    186             time.sleep(mox.IgnoreArg()).InAnyOrder().AndReturn(None)
    187         self.mox.ReplayAll()
    188         result = site_utils._get_lab_status(_FAKE_URL)
    189         self.mox.VerifyAll()
    190         self.assertEqual(None, result)
    191 
    192 
    193 class DecodeStatusTest(unittest.TestCase):
    194 
    195     """Test case for _decode_lab_status().
    196 
    197     Testing covers three distinct possible states:
    198      1. Lab is up.  All calls to _decode_lab_status() will
    199         succeed without raising an exception.
    200      2. Lab is down.  All calls to _decode_lab_status() will
    201         fail with TestLabException.
    202      3. Build disabled.  Calls to _decode_lab_status() will
    203         succeed, except that board `_DEADBUILD` will raise
    204         TestLabException.
    205 
    206     """
    207 
    208     def _assert_lab_open(self, lab_status):
    209         """Test that open status values are handled properly.
    210 
    211         Test that _decode_lab_status() succeeds when the lab status
    212         is up.
    213 
    214         @param lab_status JSON value describing lab status.
    215 
    216         """
    217         site_utils._decode_lab_status(lab_status, _LIVEBUILD)
    218         site_utils._decode_lab_status(lab_status, _DEADBUILD)
    219 
    220 
    221     def _assert_lab_closed(self, lab_status):
    222         """Test that closed status values are handled properly.
    223 
    224         Test that _decode_lab_status() raises TestLabException
    225         when the lab status is down.
    226 
    227         @param lab_status JSON value describing lab status.
    228 
    229         """
    230         with self.assertRaises(site_utils.TestLabException):
    231             site_utils._decode_lab_status(lab_status, _LIVEBUILD)
    232         with self.assertRaises(site_utils.TestLabException):
    233             site_utils._decode_lab_status(lab_status, _DEADBUILD)
    234 
    235 
    236     def _assert_lab_deadbuild(self, lab_status):
    237         """Test that disabled builds are handled properly.
    238 
    239         Test that _decode_lab_status() raises TestLabException
    240         for build `_DEADBUILD` and succeeds otherwise.
    241 
    242         @param lab_status JSON value describing lab status.
    243 
    244         """
    245         site_utils._decode_lab_status(lab_status, _LIVEBUILD)
    246         with self.assertRaises(site_utils.TestLabException):
    247             site_utils._decode_lab_status(lab_status, _DEADBUILD)
    248 
    249 
    250     def _assert_lab_status(self, test_values, checker):
    251         """General purpose test for _decode_lab_status().
    252 
    253         Decode each JSON string in `test_values`, and call the
    254         `checker` function to test the corresponding status is
    255         correctly handled.
    256 
    257         @param test_values Array of JSON encoded strings representing
    258                            lab status.
    259         @param checker Function to be called against each of the lab
    260                        status values in the `test_values` array.
    261 
    262         """
    263         for s in test_values:
    264             lab_status = json.loads(s)
    265             checker(lab_status)
    266 
    267 
    268     def test_open_lab(self):
    269         """Test that open lab status values are handled correctly."""
    270         self._assert_lab_status(_OPEN_STATUS_VALUES,
    271                                 self._assert_lab_open)
    272 
    273 
    274     def test_closed_lab(self):
    275         """Test that closed lab status values are handled correctly."""
    276         self._assert_lab_status(_CLOSED_STATUS_VALUES,
    277                                 self._assert_lab_closed)
    278 
    279 
    280     def test_dead_build(self):
    281         """Test that disabled builds are handled correctly."""
    282         self._assert_lab_status(_DEADBUILD_STATUS_VALUES,
    283                                 self._assert_lab_deadbuild)
    284 
    285 
    286 class CheckStatusTest(mox.MoxTestBase):
    287 
    288     """Test case for `check_lab_status()`.
    289 
    290     We mock out dependencies on `global_config.global_config()`,
    291     `_get_lab_status()` and confirm that the function succeeds or
    292     fails as expected.
    293 
    294     N.B.  We don't mock `_decode_lab_status()`; if DecodeStatusTest
    295     is failing, this test may fail, too.
    296 
    297     """
    298 
    299     def setUp(self):
    300         super(CheckStatusTest, self).setUp()
    301         self.mox.StubOutWithMock(global_config.global_config,
    302                                  'get_config_value')
    303         self.mox.StubOutWithMock(site_utils, '_get_lab_status')
    304 
    305 
    306     def _setup_not_cautotest(self):
    307         """Set up to mock the "we're not on cautotest" case."""
    308         global_config.global_config.get_config_value(
    309                 'SERVER', 'hostname').AndReturn('not-cautotest')
    310 
    311 
    312     def _setup_no_status(self):
    313         """Set up to mock lab status as unavailable."""
    314         global_config.global_config.get_config_value(
    315                 'SERVER', 'hostname').AndReturn('cautotest')
    316         global_config.global_config.get_config_value(
    317                 'CROS', 'lab_status_url').AndReturn(_FAKE_URL)
    318         site_utils._get_lab_status(_FAKE_URL).AndReturn(None)
    319 
    320 
    321     def _setup_lab_status(self, json_string):
    322         """Set up to mock a given lab status.
    323 
    324         @param json_string JSON string for the JSON object to return
    325                            from `_get_lab_status()`.
    326 
    327         """
    328         global_config.global_config.get_config_value(
    329                 'SERVER', 'hostname').AndReturn('cautotest')
    330         global_config.global_config.get_config_value(
    331                 'CROS', 'lab_status_url').AndReturn(_FAKE_URL)
    332         json_value = json.loads(json_string)
    333         site_utils._get_lab_status(_FAKE_URL).AndReturn(json_value)
    334 
    335 
    336     def _try_check_status(self, build):
    337         """Test calling check_lab_status() with `build`."""
    338         try:
    339             self.mox.ReplayAll()
    340             site_utils.check_lab_status(build)
    341         finally:
    342             self.mox.VerifyAll()
    343 
    344 
    345     def test_non_cautotest(self):
    346         """Test a call with a build when the host isn't cautotest."""
    347         self._setup_not_cautotest()
    348         self._try_check_status(_LIVEBUILD)
    349 
    350 
    351     def test_no_lab_status(self):
    352         """Test with a build when `_get_lab_status()` returns `None`."""
    353         self._setup_no_status()
    354         self._try_check_status(_LIVEBUILD)
    355 
    356 
    357     def test_lab_up_live_build(self):
    358         """Test lab open with a build specified."""
    359         self._setup_lab_status(_OPEN_STATUS_VALUES[0])
    360         self._try_check_status(_LIVEBUILD)
    361 
    362 
    363     def test_lab_down_live_build(self):
    364         """Test lab closed with a build specified."""
    365         self._setup_lab_status(_CLOSED_STATUS_VALUES[0])
    366         with self.assertRaises(site_utils.TestLabException):
    367             self._try_check_status(_LIVEBUILD)
    368 
    369 
    370     def test_build_disabled_live_build(self):
    371         """Test build disabled with a live build specified."""
    372         self._setup_lab_status(_DEADBUILD_STATUS_VALUES[0])
    373         self._try_check_status(_LIVEBUILD)
    374 
    375 
    376     def test_build_disabled_dead_build(self):
    377         """Test build disabled with the disabled build specified."""
    378         self._setup_lab_status(_DEADBUILD_STATUS_VALUES[0])
    379         with self.assertRaises(site_utils.TestLabException):
    380             self._try_check_status(_DEADBUILD)
    381 
    382 
    383 if __name__ == '__main__':
    384     unittest.main()
    385