Home | History | Annotate | Download | only in afe
      1 #!/usr/bin/python
      2 #
      3 # Copyright (c) 2016 The Chromium OS Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """Unit tests for frontend/afe/moblab_rpc_interface.py."""
      8 
      9 import __builtin__
     10 # The boto module is only available/used in Moblab for validation of cloud
     11 # storage access. The module is not available in the test lab environment,
     12 # and the import error is handled.
     13 try:
     14     import boto
     15 except ImportError:
     16     boto = None
     17 import ConfigParser
     18 import logging
     19 import mox
     20 import StringIO
     21 import unittest
     22 
     23 import common
     24 
     25 from autotest_lib.client.common_lib import error
     26 from autotest_lib.client.common_lib import global_config
     27 from autotest_lib.client.common_lib import lsbrelease_utils
     28 from autotest_lib.frontend import setup_django_environment
     29 from autotest_lib.frontend.afe import frontend_test_utils
     30 from autotest_lib.frontend.afe import moblab_rpc_interface
     31 from autotest_lib.frontend.afe import rpc_utils
     32 from autotest_lib.server import utils
     33 from autotest_lib.server.hosts import moblab_host
     34 
     35 
     36 class MoblabRpcInterfaceTest(mox.MoxTestBase,
     37                              frontend_test_utils.FrontendTestMixin):
     38     """Unit tests for functions in moblab_rpc_interface.py."""
     39 
     40     def setUp(self):
     41         super(MoblabRpcInterfaceTest, self).setUp()
     42         self._frontend_common_setup(fill_data=False)
     43 
     44 
     45     def tearDown(self):
     46         self._frontend_common_teardown()
     47 
     48 
     49     def setIsMoblab(self, is_moblab):
     50         """Set utils.is_moblab result.
     51 
     52         @param is_moblab: Value to have utils.is_moblab to return.
     53         """
     54         self.mox.StubOutWithMock(utils, 'is_moblab')
     55         utils.is_moblab().AndReturn(is_moblab)
     56 
     57 
     58     def _mockReadFile(self, path, lines=[]):
     59         """Mock out reading a file line by line.
     60 
     61         @param path: Path of the file we are mock reading.
     62         @param lines: lines of the mock file that will be returned when
     63                       readLine() is called.
     64         """
     65         mockFile = self.mox.CreateMockAnything()
     66         for line in lines:
     67             mockFile.readline().AndReturn(line)
     68         mockFile.readline()
     69         mockFile.close()
     70         open(path).AndReturn(mockFile)
     71 
     72 
     73     def testMoblabOnlyDecorator(self):
     74         """Ensure the moblab only decorator gates functions properly."""
     75         self.setIsMoblab(False)
     76         self.mox.ReplayAll()
     77         self.assertRaises(error.RPCException,
     78                           moblab_rpc_interface.get_config_values)
     79 
     80 
     81     def testGetConfigValues(self):
     82         """Ensure that the config object is properly converted to a dict."""
     83         self.setIsMoblab(True)
     84         config_mock = self.mox.CreateMockAnything()
     85         moblab_rpc_interface._CONFIG = config_mock
     86         config_mock.get_sections().AndReturn(['section1', 'section2'])
     87         config_mock.config = self.mox.CreateMockAnything()
     88         config_mock.config.items('section1').AndReturn([('item1', 'value1'),
     89                                                         ('item2', 'value2')])
     90         config_mock.config.items('section2').AndReturn([('item3', 'value3'),
     91                                                         ('item4', 'value4')])
     92 
     93         rpc_utils.prepare_for_serialization(
     94             {'section1' : [('item1', 'value1'),
     95                            ('item2', 'value2')],
     96              'section2' : [('item3', 'value3'),
     97                            ('item4', 'value4')]})
     98         self.mox.ReplayAll()
     99         moblab_rpc_interface.get_config_values()
    100 
    101 
    102     def testUpdateConfig(self):
    103         """Ensure that updating the config works as expected."""
    104         self.setIsMoblab(True)
    105         moblab_rpc_interface.os = self.mox.CreateMockAnything()
    106 
    107         self.mox.StubOutWithMock(__builtin__, 'open')
    108         self._mockReadFile(global_config.DEFAULT_CONFIG_FILE)
    109 
    110         self.mox.StubOutWithMock(lsbrelease_utils, 'is_moblab')
    111         lsbrelease_utils.is_moblab().AndReturn(True)
    112 
    113         self._mockReadFile(global_config.DEFAULT_MOBLAB_FILE,
    114                            ['[section1]', 'item1: value1'])
    115 
    116         moblab_rpc_interface.os = self.mox.CreateMockAnything()
    117         moblab_rpc_interface.os.path = self.mox.CreateMockAnything()
    118         moblab_rpc_interface.os.path.exists(
    119                 moblab_rpc_interface._CONFIG.shadow_file).AndReturn(
    120                 True)
    121         mockShadowFile = self.mox.CreateMockAnything()
    122         mockShadowFileContents = StringIO.StringIO()
    123         mockShadowFile.__enter__().AndReturn(mockShadowFileContents)
    124         mockShadowFile.__exit__(mox.IgnoreArg(), mox.IgnoreArg(),
    125                                 mox.IgnoreArg())
    126         open(moblab_rpc_interface._CONFIG.shadow_file,
    127              'w').AndReturn(mockShadowFile)
    128         moblab_rpc_interface.os.system('sudo reboot')
    129 
    130         self.mox.ReplayAll()
    131         moblab_rpc_interface.update_config_handler(
    132                 {'section1' : [('item1', 'value1'),
    133                                ('item2', 'value2')],
    134                  'section2' : [('item3', 'value3'),
    135                                ('item4', 'value4')]})
    136 
    137         # item1 should not be in the new shadow config as its updated value
    138         # matches the original config's value.
    139         self.assertEquals(
    140                 mockShadowFileContents.getvalue(),
    141                 '[section2]\nitem3 = value3\nitem4 = value4\n\n'
    142                 '[section1]\nitem2 = value2\n\n')
    143 
    144 
    145     def testResetConfig(self):
    146         """Ensure that reset opens the shadow_config file for writing."""
    147         self.setIsMoblab(True)
    148         config_mock = self.mox.CreateMockAnything()
    149         moblab_rpc_interface._CONFIG = config_mock
    150         config_mock.shadow_file = 'shadow_config.ini'
    151         self.mox.StubOutWithMock(__builtin__, 'open')
    152         mockFile = self.mox.CreateMockAnything()
    153         file_contents = self.mox.CreateMockAnything()
    154         mockFile.__enter__().AndReturn(file_contents)
    155         mockFile.__exit__(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg())
    156         open(config_mock.shadow_file, 'w').AndReturn(mockFile)
    157         moblab_rpc_interface.os = self.mox.CreateMockAnything()
    158         moblab_rpc_interface.os.system('sudo reboot')
    159         self.mox.ReplayAll()
    160         moblab_rpc_interface.reset_config_settings()
    161 
    162 
    163     def testSetBotoKey(self):
    164         """Ensure that the botokey path supplied is copied correctly."""
    165         self.setIsMoblab(True)
    166         boto_key = '/tmp/boto'
    167         moblab_rpc_interface.os.path = self.mox.CreateMockAnything()
    168         moblab_rpc_interface.os.path.exists(boto_key).AndReturn(
    169                 True)
    170         moblab_rpc_interface.shutil = self.mox.CreateMockAnything()
    171         moblab_rpc_interface.shutil.copyfile(
    172                 boto_key, moblab_rpc_interface.MOBLAB_BOTO_LOCATION)
    173         self.mox.ReplayAll()
    174         moblab_rpc_interface.set_boto_key(boto_key)
    175 
    176 
    177     def testSetLaunchControlKey(self):
    178         """Ensure that the Launch Control key path supplied is copied correctly.
    179         """
    180         self.setIsMoblab(True)
    181         launch_control_key = '/tmp/launch_control'
    182         moblab_rpc_interface.os = self.mox.CreateMockAnything()
    183         moblab_rpc_interface.os.path = self.mox.CreateMockAnything()
    184         moblab_rpc_interface.os.path.exists(launch_control_key).AndReturn(
    185                 True)
    186         moblab_rpc_interface.shutil = self.mox.CreateMockAnything()
    187         moblab_rpc_interface.shutil.copyfile(
    188                 launch_control_key,
    189                 moblab_host.MOBLAB_LAUNCH_CONTROL_KEY_LOCATION)
    190         moblab_rpc_interface.os.system('sudo restart moblab-devserver-init')
    191         self.mox.ReplayAll()
    192         moblab_rpc_interface.set_launch_control_key(launch_control_key)
    193 
    194 
    195     def testGetNetworkInfo(self):
    196         """Ensure the network info is properly converted to a dict."""
    197         self.setIsMoblab(True)
    198 
    199         self.mox.StubOutWithMock(moblab_rpc_interface, '_get_network_info')
    200         moblab_rpc_interface._get_network_info().AndReturn(('10.0.0.1', True))
    201         self.mox.StubOutWithMock(rpc_utils, 'prepare_for_serialization')
    202 
    203         rpc_utils.prepare_for_serialization(
    204                {'is_connected': True, 'server_ips': ['10.0.0.1']})
    205         self.mox.ReplayAll()
    206         moblab_rpc_interface.get_network_info()
    207         self.mox.VerifyAll()
    208 
    209 
    210     def testGetNetworkInfoWithNoIp(self):
    211         """Queries network info with no public IP address."""
    212         self.setIsMoblab(True)
    213 
    214         self.mox.StubOutWithMock(moblab_rpc_interface, '_get_network_info')
    215         moblab_rpc_interface._get_network_info().AndReturn((None, False))
    216         self.mox.StubOutWithMock(rpc_utils, 'prepare_for_serialization')
    217 
    218         rpc_utils.prepare_for_serialization(
    219                {'is_connected': False})
    220         self.mox.ReplayAll()
    221         moblab_rpc_interface.get_network_info()
    222         self.mox.VerifyAll()
    223 
    224 
    225     def testGetNetworkInfoWithNoConnectivity(self):
    226         """Queries network info with public IP address but no connectivity."""
    227         self.setIsMoblab(True)
    228 
    229         self.mox.StubOutWithMock(moblab_rpc_interface, '_get_network_info')
    230         moblab_rpc_interface._get_network_info().AndReturn(('10.0.0.1', False))
    231         self.mox.StubOutWithMock(rpc_utils, 'prepare_for_serialization')
    232 
    233         rpc_utils.prepare_for_serialization(
    234                {'is_connected': False, 'server_ips': ['10.0.0.1']})
    235         self.mox.ReplayAll()
    236         moblab_rpc_interface.get_network_info()
    237         self.mox.VerifyAll()
    238 
    239 
    240     def testGetCloudStorageInfo(self):
    241         """Ensure the cloud storage info is properly converted to a dict."""
    242         self.setIsMoblab(True)
    243         config_mock = self.mox.CreateMockAnything()
    244         moblab_rpc_interface._CONFIG = config_mock
    245         config_mock.get_config_value(
    246             'CROS', 'image_storage_server').AndReturn('gs://bucket1')
    247         config_mock.get_config_value(
    248             'CROS', 'results_storage_server', default=None).AndReturn(
    249                     'gs://bucket2')
    250         self.mox.StubOutWithMock(moblab_rpc_interface, '_get_boto_config')
    251         moblab_rpc_interface._get_boto_config().AndReturn(config_mock)
    252         config_mock.sections().AndReturn(['Credentials', 'b'])
    253         config_mock.options('Credentials').AndReturn(
    254             ['gs_access_key_id', 'gs_secret_access_key'])
    255         config_mock.get(
    256             'Credentials', 'gs_access_key_id').AndReturn('key')
    257         config_mock.get(
    258             'Credentials', 'gs_secret_access_key').AndReturn('secret')
    259         rpc_utils.prepare_for_serialization(
    260                 {
    261                     'gs_access_key_id': 'key',
    262                     'gs_secret_access_key' : 'secret',
    263                     'use_existing_boto_file': True,
    264                     'image_storage_server' : 'gs://bucket1',
    265                     'results_storage_server' : 'gs://bucket2'
    266                 })
    267         self.mox.ReplayAll()
    268         moblab_rpc_interface.get_cloud_storage_info()
    269         self.mox.VerifyAll()
    270 
    271 
    272     def testValidateCloudStorageInfo(self):
    273         """ Ensure the cloud storage info validation flow."""
    274         self.setIsMoblab(True)
    275         cloud_storage_info = {
    276             'use_existing_boto_file': False,
    277             'gs_access_key_id': 'key',
    278             'gs_secret_access_key': 'secret',
    279             'image_storage_server': 'gs://bucket1',
    280             'results_storage_server': 'gs://bucket2'}
    281         self.mox.StubOutWithMock(moblab_rpc_interface, '_is_valid_boto_key')
    282         self.mox.StubOutWithMock(moblab_rpc_interface, '_is_valid_bucket')
    283         moblab_rpc_interface._is_valid_boto_key(
    284                 'key', 'secret').AndReturn((True, None))
    285         moblab_rpc_interface._is_valid_bucket(
    286                 'key', 'secret', 'bucket1').AndReturn((True, None))
    287         moblab_rpc_interface._is_valid_bucket(
    288                 'key', 'secret', 'bucket2').AndReturn((True, None))
    289         rpc_utils.prepare_for_serialization(
    290                 {'status_ok': True })
    291         self.mox.ReplayAll()
    292         moblab_rpc_interface.validate_cloud_storage_info(cloud_storage_info)
    293         self.mox.VerifyAll()
    294 
    295 
    296     def testGetBucketNameFromUrl(self):
    297         """Gets bucket name from bucket URL."""
    298         self.assertEquals(
    299             'bucket_name-123',
    300             moblab_rpc_interface._get_bucket_name_from_url(
    301                     'gs://bucket_name-123'))
    302         self.assertEquals(
    303             'bucket_name-123',
    304             moblab_rpc_interface._get_bucket_name_from_url(
    305                     'gs://bucket_name-123/'))
    306         self.assertEquals(
    307             'bucket_name-123',
    308             moblab_rpc_interface._get_bucket_name_from_url(
    309                     'gs://bucket_name-123/a/b/c'))
    310         self.assertIsNone(moblab_rpc_interface._get_bucket_name_from_url(
    311             'bucket_name-123/a/b/c'))
    312 
    313 
    314     def testIsValidBotoKeyValid(self):
    315         """Tests the boto key validation flow."""
    316         if boto is None:
    317             logging.info('skip test since boto module not installed')
    318             return
    319         conn = self.mox.CreateMockAnything()
    320         self.mox.StubOutWithMock(boto, 'connect_gs')
    321         boto.connect_gs('key', 'secret').AndReturn(conn)
    322         conn.get_all_buckets().AndReturn(['a', 'b'])
    323         conn.close()
    324         self.mox.ReplayAll()
    325         valid, details = moblab_rpc_interface._is_valid_boto_key('key', 'secret')
    326         self.assertTrue(valid)
    327         self.mox.VerifyAll()
    328 
    329 
    330     def testIsValidBotoKeyInvalid(self):
    331         """Tests the boto key validation with invalid key."""
    332         if boto is None:
    333             logging.info('skip test since boto module not installed')
    334             return
    335         conn = self.mox.CreateMockAnything()
    336         self.mox.StubOutWithMock(boto, 'connect_gs')
    337         boto.connect_gs('key', 'secret').AndReturn(conn)
    338         conn.get_all_buckets().AndRaise(
    339                 boto.exception.GSResponseError('bad', 'reason'))
    340         conn.close()
    341         self.mox.ReplayAll()
    342         valid, details = moblab_rpc_interface._is_valid_boto_key('key', 'secret')
    343         self.assertFalse(valid)
    344         self.assertEquals('The boto access key is not valid', details)
    345         self.mox.VerifyAll()
    346 
    347 
    348     def testIsValidBucketValid(self):
    349         """Tests the bucket vaildation flow."""
    350         if boto is None:
    351             logging.info('skip test since boto module not installed')
    352             return
    353         conn = self.mox.CreateMockAnything()
    354         self.mox.StubOutWithMock(boto, 'connect_gs')
    355         boto.connect_gs('key', 'secret').AndReturn(conn)
    356         conn.lookup('bucket').AndReturn('bucket')
    357         conn.close()
    358         self.mox.ReplayAll()
    359         valid, details = moblab_rpc_interface._is_valid_bucket(
    360                 'key', 'secret', 'bucket')
    361         self.assertTrue(valid)
    362         self.mox.VerifyAll()
    363 
    364 
    365     def testIsValidBucketInvalid(self):
    366         """Tests the bucket validation flow with invalid key."""
    367         if boto is None:
    368             logging.info('skip test since boto module not installed')
    369             return
    370         conn = self.mox.CreateMockAnything()
    371         self.mox.StubOutWithMock(boto, 'connect_gs')
    372         boto.connect_gs('key', 'secret').AndReturn(conn)
    373         conn.lookup('bucket').AndReturn(None)
    374         conn.close()
    375         self.mox.ReplayAll()
    376         valid, details = moblab_rpc_interface._is_valid_bucket(
    377                 'key', 'secret', 'bucket')
    378         self.assertFalse(valid)
    379         self.assertEquals("Bucket bucket does not exist.", details)
    380         self.mox.VerifyAll()
    381 
    382 
    383     def testGetShadowConfigFromPartialUpdate(self):
    384         """Tests getting shadow configuration based on partial upate."""
    385         partial_config = {
    386                 'section1': [
    387                     ('opt1', 'value1'),
    388                     ('opt2', 'value2'),
    389                     ('opt3', 'value3'),
    390                     ('opt4', 'value4'),
    391                     ]
    392                 }
    393         shadow_config_str = "[section1]\nopt2 = value2_1\nopt4 = value4_1"
    394         shadow_config = ConfigParser.ConfigParser()
    395         shadow_config.readfp(StringIO.StringIO(shadow_config_str))
    396         original_config = self.mox.CreateMockAnything()
    397         self.mox.StubOutWithMock(moblab_rpc_interface, '_read_original_config')
    398         self.mox.StubOutWithMock(moblab_rpc_interface, '_read_raw_config')
    399         moblab_rpc_interface._read_original_config().AndReturn(original_config)
    400         moblab_rpc_interface._read_raw_config(
    401                 moblab_rpc_interface._CONFIG.shadow_file).AndReturn(shadow_config)
    402         original_config.get_config_value(
    403                 'section1', 'opt1',
    404                 allow_blank=True, default='').AndReturn('value1')
    405         original_config.get_config_value(
    406                 'section1', 'opt2',
    407                 allow_blank=True, default='').AndReturn('value2')
    408         original_config.get_config_value(
    409                 'section1', 'opt3',
    410                 allow_blank=True, default='').AndReturn('blah')
    411         original_config.get_config_value(
    412                 'section1', 'opt4',
    413                 allow_blank=True, default='').AndReturn('blah')
    414         self.mox.ReplayAll()
    415         shadow_config = moblab_rpc_interface._get_shadow_config_from_partial_update(
    416                 partial_config)
    417         # opt1 same as the original.
    418         self.assertFalse(shadow_config.has_option('section1', 'opt1'))
    419         # opt2 reverts back to original
    420         self.assertFalse(shadow_config.has_option('section1', 'opt2'))
    421         # opt3 is updated from original.
    422         self.assertEquals('value3', shadow_config.get('section1', 'opt3'))
    423         # opt3 in shadow but updated again.
    424         self.assertEquals('value4', shadow_config.get('section1', 'opt4'))
    425         self.mox.VerifyAll()
    426 
    427 
    428     def testGetShadowConfigFromPartialUpdateWithNewSection(self):
    429         """
    430         Test getting shadown configuration based on partial update with new section.
    431         """
    432         partial_config = {
    433                 'section2': [
    434                     ('opt5', 'value5'),
    435                     ('opt6', 'value6'),
    436                     ],
    437                 }
    438         shadow_config_str = "[section1]\nopt2 = value2_1\n"
    439         shadow_config = ConfigParser.ConfigParser()
    440         shadow_config.readfp(StringIO.StringIO(shadow_config_str))
    441         original_config = self.mox.CreateMockAnything()
    442         self.mox.StubOutWithMock(moblab_rpc_interface, '_read_original_config')
    443         self.mox.StubOutWithMock(moblab_rpc_interface, '_read_raw_config')
    444         moblab_rpc_interface._read_original_config().AndReturn(original_config)
    445         moblab_rpc_interface._read_raw_config(
    446             moblab_rpc_interface._CONFIG.shadow_file).AndReturn(shadow_config)
    447         original_config.get_config_value(
    448                 'section2', 'opt5',
    449                 allow_blank=True, default='').AndReturn('value5')
    450         original_config.get_config_value(
    451                 'section2', 'opt6',
    452                 allow_blank=True, default='').AndReturn('blah')
    453         self.mox.ReplayAll()
    454         shadow_config = moblab_rpc_interface._get_shadow_config_from_partial_update(
    455                 partial_config)
    456         # opt2 is still in shadow
    457         self.assertEquals('value2_1', shadow_config.get('section1', 'opt2'))
    458         # opt5 is not changed.
    459         self.assertFalse(shadow_config.has_option('section2', 'opt5'))
    460         # opt6 is updated.
    461         self.assertEquals('value6', shadow_config.get('section2', 'opt6'))
    462         self.mox.VerifyAll()
    463 
    464 
    465 if __name__ == '__main__':
    466     unittest.main()
    467