Home | History | Annotate | Download | only in lxc
      1 #!/usr/bin/python
      2 # Copyright 2015 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Function tests of lxc module. To be able to run this test, following setup
      7 is required:
      8   1. lxc is installed.
      9   2. Autotest code exists in /usr/local/autotest, with site-packages installed.
     10      (run utils/build_externals.py)
     11   3. The user runs the test should have sudo access. Run the test with sudo.
     12 Note that the test does not require Autotest database and frontend.
     13 """
     14 
     15 
     16 import argparse
     17 import logging
     18 import os
     19 import tempfile
     20 
     21 import common
     22 from autotest_lib.client.bin import utils
     23 from autotest_lib.client.common_lib import error
     24 from autotest_lib.site_utils import lxc
     25 from autotest_lib.site_utils.lxc import unittest_setup
     26 
     27 
     28 TEST_JOB_ID = 123
     29 TEST_JOB_FOLDER = '123-debug_user'
     30 # Create a temp directory for functional tests. The directory is not under /tmp
     31 # for Moblab to be able to run the test.
     32 TEMP_DIR = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH,
     33                             prefix='container_test_')
     34 RESULT_PATH = os.path.join(TEMP_DIR, 'results', str(TEST_JOB_ID))
     35 # Link to download a test package of autotest server package.
     36 # Ideally the test should stage a build on devserver and download the
     37 # autotest_server_package from devserver. This test is focused on testing
     38 # container, so it's prefered to avoid dependency on devserver.
     39 AUTOTEST_SERVER_PKG = ('http://storage.googleapis.com/abci-ssp/'
     40                        'autotest-containers/autotest_server_package.tar.bz2')
     41 
     42 # Test log file to be created in result folder, content is `test`.
     43 TEST_LOG = 'test.log'
     44 # Name of test script file to run in container.
     45 TEST_SCRIPT = 'test.py'
     46 # Test script to run in container to verify autotest code setup.
     47 TEST_SCRIPT_CONTENT = """
     48 import socket
     49 import sys
     50 
     51 # Test import
     52 import common
     53 import chromite
     54 
     55 # This test has to be before the import of autotest_lib, because ts_mon requires
     56 # httplib2 module in chromite/third_party. The one in Autotest site-packages is
     57 # out dated.
     58 %(ts_mon_test)s
     59 
     60 from autotest_lib.server import utils
     61 from autotest_lib.site_utils import lxc
     62 
     63 with open(sys.argv[1], 'w') as f:
     64     f.write('test')
     65 
     66 # Confirm hostname starts with `test-`
     67 if not socket.gethostname().startswith('test-'):
     68     raise Exception('The container\\\'s hostname must start with `test-`.')
     69 
     70 # Test installing packages
     71 lxc.install_packages(['atop'], ['acora'])
     72 
     73 """
     74 
     75 TEST_SCRIPT_CONTENT_TS_MON = """
     76 # Test ts_mon metrics can be set up.
     77 from chromite.lib import ts_mon_config
     78 ts_mon_config.SetupTsMonGlobalState('some_test', suppress_exception=False)
     79 """
     80 
     81 CREATE_FAKE_TS_MON_CONFIG_SCRIPT = 'create_fake_key.py'
     82 
     83 CREATE_FAKE_TS_MON_CONFIG_SCRIPT_CONTENT = """
     84 import os
     85 import rsa
     86 
     87 EXPECTED_TS_MON_CONFIG_NAME = '/etc/chrome-infra/ts-mon.json'
     88 
     89 FAKE_TS_MON_CONFIG_CONTENT = '''
     90     {
     91         "credentials":"/tmp/service_account_prodx_mon.json",
     92         "endpoint":"https://xxx.googleapis.com/v1:insert",
     93         "use_new_proto": true
     94     }'''
     95 
     96 FAKE_SERVICE_ACCOUNT_CRED_JSON = '''
     97     {
     98         "type": "service_account",
     99         "project_id": "test_project",
    100         "private_key_id": "aaa",
    101         "private_key": "%s",
    102         "client_email": "xxx",
    103         "client_id": "111",
    104         "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    105         "token_uri": "https://accounts.google.com/o/oauth2/token",
    106         "auth_provider_x509_cert_url":
    107                 "https://www.googleapis.com/oauth2/v1/certs",
    108         "client_x509_cert_url":
    109                 "https://www.googleapis.com/robot/v1/metadata/x509/xxx"
    110     }'''
    111 
    112 
    113 TEST_KEY = '''------BEGIN PRIVATE KEY-----
    114 MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzg4K2SXqf9LAM
    115 52a/t2HfpY5y49sbrgRb1llP6c8RVWhUX/pGdjbcIM97+1CJEWBN8Vmraoe4+71o
    116 1idTPehJfHRNeyXQUnro8CmnSxE9tLHtdKj0pzvO+yqT66O6Iw1aUAIX+dG4Us9Q
    117 Z22ypFHaJ74lKw9JFwAFTJ/TF1rXUXqgufYTNNqP3Ra7wCHF8BmtjwRYAlvsR9CO
    118 c4eVC1+qhq/8/EOMCgF/rsbZW93r/nz5xgsSX0k6WkAz5WX2mniHfmBFpmr039jZ
    119 0eI1mEMGDAYuUn05++dNveo/ZOZj3wBlFzyfNSeeWJB5SdKPTvN3H/Iu0Aw+Rtb6
    120 szwNClaFAgMBAAECggEAHZ8cjVRUJ/tiJorzlTyfKZ6hwhsPv4JIRVg6LhnceZWA
    121 jPW2cHSWyl2epyx55lhH7iyeeY7vXOqrX1aBMDb1stSWw2dH/tdxYSkqEmksa+R6
    122 fL6kl5RV5epjpPt77Z3VmPq9UbP/M310qKWcgB8lw4wN0AfKMqsZLYauk9BVhNRu
    123 Bgah9O7BmcXS+mp49w0Xyfo1UBvzW8R6UnBhHbf9aOY8ObMD0Jj/wDjlYMqSSIKR
    124 9/8GZWQEKe6q0PyRRdNNtdzbpBrR0fIw6/T9pfDR2fBAcpNvD50eJk2jRiRDTWFJ
    125 rVSc0bvZFb74Rc3LbMSXW/6Kb7I2IG1XsWw7nxp92QKBgQDgzdIxZrkNZ3Tbuzng
    126 SG4atjnaCXoekOHK7VZVYd30S0AAizeGu1sjpUVQgsf+qkFskXAQp2/2f+Wiuq2G
    127 +nJYvXwZ/r9IcUs/oD3Fa2ezCVz1N/HOSPFAZK9XZuZbL8sXEYIPGJWH5F8Sanmb
    128 xNp9IUynlpwgM2JlZNeTCkv4PQKBgQDMbL/AF3LSpKvwi+QvYVkX/gChQmNMr4pP
    129 TM/GI4D03tNrzsut3oerKMUw0c5MxonkAJpuACN6baRyBOBxRYQSt8wWkORg9iqy
    130 a7aHnQqIGRafydW1/Snhr2DJSSaViHfO0oaA1r61zgMUTnSGb3UjyxJQp65dvPac
    131 BhpR9wpz6QKBgQDR2S/CL8rEqXObfi1roREu3DYqw7f8enBb1qtFrsLbPbd0CoD9
    132 wz0zjB6lJj/9CP9jkmwTD8njR8ab3jkIDBfboJ4NQhFbVW7R6QpglH9L0Iy2189g
    133 KhUScCqBoyubqYSidxR6dQ94uATLkxsL/nmaXxBITL5XDMBoN/dIak86XQKBgDqa
    134 oo4LKtvAYZpgQFZk7gm2w693PMhrOpdpSddfrkSE7M9nRXTe6r3ivkU0oJPaBwXa
    135 Nmt6lrEuZYpaY42VhDtpfZSqjQ5PBAaKYpWWK8LAjn/YeO/nV+5fPLv3wJv1t4MP
    136 T4f4CExOdwuHQliX81kDioicyZwN5BTumvUMgW6hAoGAF29kI1KthKaHN9P1DchI
    137 qqoHb9FPdZ5I6HDQpn6fr9ut7+9kVqexUrQ2AMvcVei6gDWW6P3yDCdTKcV9qtts
    138 1JOP2aSmXvibflx/bNfnhu988qJDhJ3CCjfc79fjwntUIXNPsFmwC9W5lnlSMKHM
    139 rH4RdmnjeCIG1PZ35m/yUSU=
    140 -----END PRIVATE KEY-----'''
    141 
    142 if not os.path.exists(EXPECTED_TS_MON_CONFIG_NAME):
    143     try:
    144         os.makedirs(os.path.dirname(EXPECTED_TS_MON_CONFIG_NAME))
    145     except OSError:
    146         # Directory already exists.
    147         pass
    148 
    149     with open(EXPECTED_TS_MON_CONFIG_NAME, 'w') as f:
    150         f.write(FAKE_TS_MON_CONFIG_CONTENT)
    151     with open ('/tmp/service_account_prodx_mon.json', 'w') as f:
    152         f.write(FAKE_SERVICE_ACCOUNT_CRED_JSON % repr(TEST_KEY)[2:-1])
    153 """
    154 
    155 # Name of the test control file.
    156 TEST_CONTROL_FILE = 'attach.1'
    157 TEST_DUT = '172.27.213.193'
    158 TEST_RESULT_PATH = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER
    159 # Test autoserv command.
    160 AUTOSERV_COMMAND = (('/usr/bin/python -u /usr/local/autotest/server/autoserv '
    161                      '-p -r %(result_path)s/%(test_dut)s -m %(test_dut)s '
    162                      '-u debug_user -l test -s -P %(job_id)s-debug_user/'
    163                      '%(test_dut)s -n %(result_path)s/%(test_control_file)s '
    164                      '--verify_job_repo_url') %
    165                      {'job_id': TEST_JOB_ID,
    166                       'result_path': TEST_RESULT_PATH,
    167                       'test_dut': TEST_DUT,
    168                       'test_control_file': TEST_CONTROL_FILE})
    169 # Content of the test control file.
    170 TEST_CONTROL_CONTENT = """
    171 def run(machine):
    172     job.run_test('dummy_PassServer',
    173                  host=hosts.create_host(machine))
    174 
    175 parallel_simple(run, machines)
    176 """
    177 
    178 
    179 def setup_base(container_path):
    180     """Test setup base container works.
    181 
    182     @param bucket: ContainerBucket to interact with containers.
    183     """
    184     logging.info('Rebuild base container in folder %s.', container_path)
    185     image = lxc.BaseImage(container_path)
    186     image.setup()
    187     logging.info('Base container created: %s', image.get().name)
    188 
    189 
    190 def setup_test(bucket, container_id, skip_cleanup):
    191     """Test container can be created from base container.
    192 
    193     @param bucket: ContainerBucket to interact with containers.
    194     @param container_id: ID of the test container.
    195     @param skip_cleanup: Set to True to skip cleanup, used to troubleshoot
    196                          container failures.
    197 
    198     @return: A Container object created for the test container.
    199     """
    200     logging.info('Create test container.')
    201     os.makedirs(RESULT_PATH)
    202     container = bucket.setup_test(container_id, TEST_JOB_ID,
    203                                   AUTOTEST_SERVER_PKG, RESULT_PATH,
    204                                   skip_cleanup=skip_cleanup,
    205                                   job_folder=TEST_JOB_FOLDER,
    206                                   dut_name='192.168.0.3')
    207 
    208     # Inject "AUTOSERV/testing_mode: True" in shadow config to test autoserv.
    209     container.attach_run('echo $\'[AUTOSERV]\ntesting_mode: True\' >>'
    210                          ' /usr/local/autotest/shadow_config.ini')
    211 
    212     if not utils.is_moblab():
    213         # Create fake '/etc/chrome-infra/ts-mon.json' if it doesn't exist.
    214         create_key_script = os.path.join(
    215                 RESULT_PATH, CREATE_FAKE_TS_MON_CONFIG_SCRIPT)
    216         with open(create_key_script, 'w') as script:
    217             script.write(CREATE_FAKE_TS_MON_CONFIG_SCRIPT_CONTENT)
    218         container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER
    219         container_create_key_script = os.path.join(
    220                 container_result_path, CREATE_FAKE_TS_MON_CONFIG_SCRIPT)
    221         container.attach_run('python %s' % container_create_key_script)
    222 
    223     return container
    224 
    225 
    226 def test_share(container):
    227     """Test container can share files with the host.
    228 
    229     @param container: The test container.
    230     """
    231     logging.info('Test files written to result directory can be accessed '
    232                  'from the host running the container..')
    233     host_test_script = os.path.join(RESULT_PATH, TEST_SCRIPT)
    234     with open(host_test_script, 'w') as script:
    235         if utils.is_moblab():
    236             script.write(TEST_SCRIPT_CONTENT % {'ts_mon_test': ''})
    237         else:
    238             script.write(TEST_SCRIPT_CONTENT %
    239                          {'ts_mon_test': TEST_SCRIPT_CONTENT_TS_MON})
    240 
    241     container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER
    242     container_test_script = os.path.join(container_result_path, TEST_SCRIPT)
    243     container_test_script_dest = os.path.join('/usr/local/autotest/utils/',
    244                                               TEST_SCRIPT)
    245     container_test_log = os.path.join(container_result_path, TEST_LOG)
    246     host_test_log = os.path.join(RESULT_PATH, TEST_LOG)
    247     # Move the test script out of result folder as it needs to import common.
    248     container.attach_run('mv %s %s' % (container_test_script,
    249                                        container_test_script_dest))
    250     container.attach_run('python %s %s' % (container_test_script_dest,
    251                                            container_test_log))
    252     if not os.path.exists(host_test_log):
    253         raise Exception('Results created in container can not be accessed from '
    254                         'the host.')
    255     with open(host_test_log, 'r') as log:
    256         if log.read() != 'test':
    257             raise Exception('Failed to read the content of results in '
    258                             'container.')
    259 
    260 
    261 def test_autoserv(container):
    262     """Test container can run autoserv command.
    263 
    264     @param container: The test container.
    265     """
    266     logging.info('Test autoserv command.')
    267     logging.info('Create test control file.')
    268     host_control_file = os.path.join(RESULT_PATH, TEST_CONTROL_FILE)
    269     with open(host_control_file, 'w') as control_file:
    270         control_file.write(TEST_CONTROL_CONTENT)
    271 
    272     logging.info('Run autoserv command.')
    273     container.attach_run(AUTOSERV_COMMAND)
    274 
    275     logging.info('Confirm results are available from host.')
    276     # Read status.log to check the content is not empty.
    277     container_status_log = os.path.join(TEST_RESULT_PATH, TEST_DUT,
    278                                         'status.log')
    279     status_log = container.attach_run(command='cat %s' % container_status_log
    280                                       ).stdout
    281     if len(status_log) < 10:
    282         raise Exception('Failed to read status.log in container.')
    283 
    284 
    285 def test_package_install(container):
    286     """Test installing package in container.
    287 
    288     @param container: The test container.
    289     """
    290     # Packages are installed in TEST_SCRIPT_CONTENT. Verify the packages in
    291     # this method.
    292     container.attach_run('which atop')
    293     container.attach_run('python -c "import acora"')
    294 
    295 
    296 def test_ssh(container, remote):
    297     """Test container can run ssh to remote server.
    298 
    299     @param container: The test container.
    300     @param remote: The remote server to ssh to.
    301 
    302     @raise: error.CmdError if container can't ssh to remote server.
    303     """
    304     logging.info('Test ssh to %s.', remote)
    305     container.attach_run('ssh %s -a -x -o StrictHostKeyChecking=no '
    306                          '-o BatchMode=yes -o UserKnownHostsFile=/dev/null '
    307                          '-p 22 "true"' % remote)
    308 
    309 
    310 def parse_options():
    311     """Parse command line inputs.
    312     """
    313     parser = argparse.ArgumentParser()
    314     parser.add_argument('-d', '--dut', type=str,
    315                         help='Test device to ssh to.',
    316                         default=None)
    317     parser.add_argument('-r', '--devserver', type=str,
    318                         help='Test devserver to ssh to.',
    319                         default=None)
    320     parser.add_argument('-v', '--verbose', action='store_true',
    321                         default=False,
    322                         help='Print out ALL entries.')
    323     parser.add_argument('-s', '--skip_cleanup', action='store_true',
    324                         default=False,
    325                         help='Skip deleting test containers.')
    326     return parser.parse_args()
    327 
    328 
    329 def main(options):
    330     """main script.
    331 
    332     @param options: Options to run the script.
    333     """
    334     # Verify that the test is running as the correct user.
    335     unittest_setup.verify_user()
    336 
    337     log_level=(logging.DEBUG if options.verbose else logging.INFO)
    338     unittest_setup.setup_logging(log_level)
    339 
    340     setup_base(TEMP_DIR)
    341     bucket = lxc.ContainerBucket(TEMP_DIR)
    342 
    343     container_id = lxc.ContainerId.create(TEST_JOB_ID)
    344     container = setup_test(bucket, container_id, options.skip_cleanup)
    345     test_share(container)
    346     test_autoserv(container)
    347     if options.dut:
    348         test_ssh(container, options.dut)
    349     if options.devserver:
    350         test_ssh(container, options.devserver)
    351     # Packages are installed in TEST_SCRIPT, verify the packages are installed.
    352     test_package_install(container)
    353     logging.info('All tests passed.')
    354 
    355 
    356 if __name__ == '__main__':
    357     options = parse_options()
    358     try:
    359         main(options)
    360     except:
    361         # If the cleanup code below raises additional errors, they obfuscate the
    362         # actual error in the test.  Highlight the error to aid in debugging.
    363         logging.exception('ERROR:\n%s', error.format_error())
    364         raise
    365     finally:
    366         if not options.skip_cleanup:
    367             logging.info('Cleaning up temporary directory %s.', TEMP_DIR)
    368             try:
    369                 lxc.ContainerBucket(TEMP_DIR).destroy_all()
    370             finally:
    371                 utils.run('sudo rm -rf "%s"' % TEMP_DIR)
    372