Home | History | Annotate | Download | only in site_utils
      1 # Copyright 2015 The Chromium 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 """Function tests of lxc module. To be able to run this test, following setup
      6 is required:
      7   1. lxc is installed.
      8   2. Autotest code exists in /usr/local/autotest, with site-packages installed.
      9      (run utils/build_externals.py)
     10   3. The user runs the test should have sudo access. Run the test with sudo.
     11 Note that the test does not require Autotest database and frontend.
     12 """
     13 
     14 
     15 import argparse
     16 import logging
     17 import os
     18 import sys
     19 import tempfile
     20 import time
     21 
     22 import common
     23 from autotest_lib.client.bin import utils
     24 from autotest_lib.site_utils import lxc
     25 
     26 
     27 TEST_JOB_ID = 123
     28 TEST_JOB_FOLDER = '123-debug_user'
     29 # Create a temp directory for functional tests. The directory is not under /tmp
     30 # for Moblab to be able to run the test.
     31 TEMP_DIR = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH,
     32                             prefix='container_test_')
     33 RESULT_PATH = os.path.join(TEMP_DIR, 'results', str(TEST_JOB_ID))
     34 # Link to download a test package of autotest server package.
     35 # Ideally the test should stage a build on devserver and download the
     36 # autotest_server_package from devserver. This test is focused on testing
     37 # container, so it's prefered to avoid dependency on devserver.
     38 AUTOTEST_SERVER_PKG = ('http://storage.googleapis.com/abci-ssp/'
     39                        'autotest-containers/autotest_server_package.tar.bz2')
     40 
     41 # Test log file to be created in result folder, content is `test`.
     42 TEST_LOG = 'test.log'
     43 # Name of test script file to run in container.
     44 TEST_SCRIPT = 'test.py'
     45 # Test script to run in container to verify autotest code setup.
     46 TEST_SCRIPT_CONTENT = """
     47 import socket
     48 import sys
     49 
     50 # Test import
     51 import common
     52 import chromite
     53 from autotest_lib.server import utils
     54 from autotest_lib.site_utils import lxc
     55 
     56 with open(sys.argv[1], 'w') as f:
     57     f.write('test')
     58 
     59 # Confirm hostname starts with `test_`
     60 if not socket.gethostname().startswith('test_'):
     61     raise Exception('The container\\\'s hostname must start with `test_`.')
     62 
     63 # Test installing packages
     64 lxc.install_packages(['atop', 'libxslt-dev'], ['selenium', 'numpy'])
     65 
     66 """
     67 # Name of the test control file.
     68 TEST_CONTROL_FILE = 'attach.1'
     69 TEST_DUT = '172.27.213.193'
     70 TEST_RESULT_PATH = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER
     71 # Test autoserv command.
     72 AUTOSERV_COMMAND = (('/usr/bin/python -u /usr/local/autotest/server/autoserv '
     73                      '-p -r %(result_path)s/%(test_dut)s -m %(test_dut)s '
     74                      '-u debug_user -l test -s -P %(job_id)s-debug_user/'
     75                      '%(test_dut)s -n %(result_path)s/%(test_control_file)s '
     76                      '--verify_job_repo_url') %
     77                      {'job_id': TEST_JOB_ID,
     78                       'result_path': TEST_RESULT_PATH,
     79                       'test_dut': TEST_DUT,
     80                       'test_control_file': TEST_CONTROL_FILE})
     81 # Content of the test control file.
     82 TEST_CONTROL_CONTENT = """
     83 def run(machine):
     84     job.run_test('dummy_PassServer',
     85                  host=hosts.create_host(machine))
     86 
     87 parallel_simple(run, machines)
     88 """
     89 
     90 
     91 def setup_logging(log_level=logging.INFO):
     92     """Direct logging to stdout.
     93 
     94     @param log_level: Level of logging to redirect to stdout, default to INFO.
     95     """
     96     logger = logging.getLogger()
     97     logger.setLevel(log_level)
     98     handler = logging.StreamHandler(sys.stdout)
     99     handler.setLevel(log_level)
    100     formatter = logging.Formatter('%(asctime)s %(message)s')
    101     handler.setFormatter(formatter)
    102     logger.handlers = []
    103     logger.addHandler(handler)
    104 
    105 
    106 def setup_base(bucket):
    107     """Test setup base container works.
    108 
    109     @param bucket: ContainerBucket to interact with containers.
    110     """
    111     logging.info('Rebuild base container in folder %s.', bucket.container_path)
    112     bucket.setup_base()
    113     containers = bucket.get_all()
    114     logging.info('Containers created: %s', containers.keys())
    115 
    116 
    117 def setup_test(bucket, name, skip_cleanup):
    118     """Test container can be created from base container.
    119 
    120     @param bucket: ContainerBucket to interact with containers.
    121     @param name: Name of the test container.
    122     @param skip_cleanup: Set to True to skip cleanup, used to troubleshoot
    123                          container failures.
    124 
    125     @return: A Container object created for the test container.
    126     """
    127     logging.info('Create test container.')
    128     os.makedirs(RESULT_PATH)
    129     container = bucket.setup_test(name, TEST_JOB_ID, AUTOTEST_SERVER_PKG,
    130                                   RESULT_PATH, skip_cleanup=skip_cleanup,
    131                                   job_folder=TEST_JOB_FOLDER,
    132                                   dut_name='192.168.0.3')
    133 
    134     # Inject "AUTOSERV/testing_mode: True" in shadow config to test autoserv.
    135     container.attach_run('echo $\'[AUTOSERV]\ntesting_mode: True\' >>'
    136                          ' /usr/local/autotest/shadow_config.ini')
    137     return container
    138 
    139 
    140 def test_share(container):
    141     """Test container can share files with the host.
    142 
    143     @param container: The test container.
    144     """
    145     logging.info('Test files written to result directory can be accessed '
    146                  'from the host running the container..')
    147     host_test_script = os.path.join(RESULT_PATH, TEST_SCRIPT)
    148     with open(host_test_script, 'w') as script:
    149         script.write(TEST_SCRIPT_CONTENT)
    150 
    151     container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER
    152     container_test_script = os.path.join(container_result_path, TEST_SCRIPT)
    153     container_test_script_dest = os.path.join('/usr/local/autotest/utils/',
    154                                               TEST_SCRIPT)
    155     container_test_log = os.path.join(container_result_path, TEST_LOG)
    156     host_test_log = os.path.join(RESULT_PATH, TEST_LOG)
    157     # Move the test script out of result folder as it needs to import common.
    158     container.attach_run('mv %s %s' % (container_test_script,
    159                                        container_test_script_dest))
    160     container.attach_run('python %s %s' % (container_test_script_dest,
    161                                            container_test_log))
    162     if not os.path.exists(host_test_log):
    163         raise Exception('Results created in container can not be accessed from '
    164                         'the host.')
    165     with open(host_test_log, 'r') as log:
    166         if log.read() != 'test':
    167             raise Exception('Failed to read the content of results in '
    168                             'container.')
    169 
    170 
    171 def test_autoserv(container):
    172     """Test container can run autoserv command.
    173 
    174     @param container: The test container.
    175     """
    176     logging.info('Test autoserv command.')
    177     logging.info('Create test control file.')
    178     host_control_file = os.path.join(RESULT_PATH, TEST_CONTROL_FILE)
    179     with open(host_control_file, 'w') as control_file:
    180         control_file.write(TEST_CONTROL_CONTENT)
    181 
    182     logging.info('Run autoserv command.')
    183     container.attach_run(AUTOSERV_COMMAND)
    184 
    185     logging.info('Confirm results are available from host.')
    186     # Read status.log to check the content is not empty.
    187     container_status_log = os.path.join(TEST_RESULT_PATH, TEST_DUT,
    188                                         'status.log')
    189     status_log = container.attach_run(command='cat %s' % container_status_log
    190                                       ).stdout
    191     if len(status_log) < 10:
    192         raise Exception('Failed to read status.log in container.')
    193 
    194 
    195 def test_package_install(container):
    196     """Test installing package in container.
    197 
    198     @param container: The test container.
    199     """
    200     # Packages are installed in TEST_SCRIPT_CONTENT. Verify the packages in
    201     # this method.
    202     container.attach_run('which atop')
    203     container.attach_run('python -c "import selenium"')
    204 
    205 
    206 def test_ssh(container, remote):
    207     """Test container can run ssh to remote server.
    208 
    209     @param container: The test container.
    210     @param remote: The remote server to ssh to.
    211 
    212     @raise: error.CmdError if container can't ssh to remote server.
    213     """
    214     logging.info('Test ssh to %s.', remote)
    215     container.attach_run('ssh %s -a -x -o StrictHostKeyChecking=no '
    216                          '-o BatchMode=yes -o UserKnownHostsFile=/dev/null '
    217                          '-p 22 "true"' % remote)
    218 
    219 
    220 def parse_options():
    221     """Parse command line inputs.
    222     """
    223     parser = argparse.ArgumentParser()
    224     parser.add_argument('-d', '--dut', type=str,
    225                         help='Test device to ssh to.',
    226                         default=None)
    227     parser.add_argument('-r', '--devserver', type=str,
    228                         help='Test devserver to ssh to.',
    229                         default=None)
    230     parser.add_argument('-v', '--verbose', action='store_true',
    231                         default=False,
    232                         help='Print out ALL entries.')
    233     parser.add_argument('-s', '--skip_cleanup', action='store_true',
    234                         default=False,
    235                         help='Skip deleting test containers.')
    236     return parser.parse_args()
    237 
    238 
    239 def main(options):
    240     """main script.
    241 
    242     @param options: Options to run the script.
    243     """
    244     # Force to run the test as superuser.
    245     # TODO(dshi): crbug.com/459344 Set remove this enforcement when test
    246     # container can be unprivileged container.
    247     if utils.sudo_require_password():
    248         logging.warn('SSP requires root privilege to run commands, please '
    249                      'grant root access to this process.')
    250         utils.run('sudo true')
    251 
    252     setup_logging(log_level=(logging.DEBUG if options.verbose
    253                              else logging.INFO))
    254 
    255     bucket = lxc.ContainerBucket(TEMP_DIR)
    256 
    257     setup_base(bucket)
    258     container_test_name = (lxc.TEST_CONTAINER_NAME_FMT %
    259                            (TEST_JOB_ID, time.time(), os.getpid()))
    260     container = setup_test(bucket, container_test_name, options.skip_cleanup)
    261     test_share(container)
    262     test_autoserv(container)
    263     if options.dut:
    264         test_ssh(container, options.dut)
    265     if options.devserver:
    266         test_ssh(container, options.devserver)
    267     # Packages are installed in TEST_SCRIPT, verify the packages are installed.
    268     test_package_install(container)
    269     logging.info('All tests passed.')
    270 
    271 
    272 if __name__ == '__main__':
    273     options = parse_options()
    274     try:
    275         main(options)
    276     finally:
    277         if not options.skip_cleanup:
    278             logging.info('Cleaning up temporary directory %s.', TEMP_DIR)
    279             try:
    280                 lxc.ContainerBucket(TEMP_DIR).destroy_all()
    281             finally:
    282                 utils.run('sudo rm -rf "%s"' % TEMP_DIR)
    283