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