#!/usr/bin/python2 # Copyright 2015 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Function tests of lxc module. To be able to run this test, following setup is required: 1. lxc is installed. 2. Autotest code exists in /usr/local/autotest, with site-packages installed. (run utils/build_externals.py) 3. The user runs the test should have sudo access. Run the test with sudo. Note that the test does not require Autotest database and frontend. """ import argparse import logging import os import tempfile import common from autotest_lib.client.bin import utils from autotest_lib.client.common_lib import error from autotest_lib.site_utils import lxc from autotest_lib.site_utils.lxc import base_image from autotest_lib.site_utils.lxc import unittest_setup TEST_JOB_ID = 123 TEST_JOB_FOLDER = '123-debug_user' # Create a temp directory for functional tests. The directory is not under /tmp # for Moblab to be able to run the test. # But first, ensure that the containing directory exists: if not os.path.exists(lxc.DEFAULT_CONTAINER_PATH): os.makedirs(lxc.DEFAULT_CONTAINER_PATH) TEMP_DIR = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH, prefix='container_test_') RESULT_PATH = os.path.join(TEMP_DIR, 'results', str(TEST_JOB_ID)) # Link to download a test package of autotest server package. # Ideally the test should stage a build on devserver and download the # autotest_server_package from devserver. This test is focused on testing # container, so it's prefered to avoid dependency on devserver. AUTOTEST_SERVER_PKG = ('http://storage.googleapis.com/abci-ssp/' 'autotest-containers/autotest_server_package.tar.bz2') # Test log file to be created in result folder, content is `test`. TEST_LOG = 'test.log' # Name of test script file to run in container. TEST_SCRIPT = 'test.py' # Test script to run in container to verify autotest code setup. TEST_SCRIPT_CONTENT = """ import socket import sys # Test import import common import chromite # This test has to be before the import of autotest_lib, because ts_mon requires # httplib2 module in chromite/third_party. The one in Autotest site-packages is # out dated. %(ts_mon_test)s from autotest_lib.server import utils from autotest_lib.site_utils import lxc with open(sys.argv[1], 'w') as f: f.write('test') # Confirm hostname starts with `test-` if not socket.gethostname().startswith('test-'): raise Exception('The container\\\'s hostname must start with `test-`.') # Test installing packages lxc.install_packages(['atop'], ['acora']) """ TEST_SCRIPT_CONTENT_TS_MON = """ # Test ts_mon metrics can be set up. from chromite.lib import ts_mon_config ts_mon_config.SetupTsMonGlobalState('some_test', suppress_exception=False) """ CREATE_FAKE_TS_MON_CONFIG_SCRIPT = 'create_fake_key.py' CREATE_FAKE_TS_MON_CONFIG_SCRIPT_CONTENT = """ import os import rsa EXPECTED_TS_MON_CONFIG_NAME = '/etc/chrome-infra/ts-mon.json' FAKE_TS_MON_CONFIG_CONTENT = ''' { "credentials":"/tmp/service_account_prodx_mon.json", "endpoint":"https://xxx.googleapis.com/v1:insert", "use_new_proto": true }''' FAKE_SERVICE_ACCOUNT_CRED_JSON = ''' { "type": "service_account", "project_id": "test_project", "private_key_id": "aaa", "private_key": "%s", "client_email": "xxx", "client_id": "111", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxx" }''' TEST_KEY = '''------BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzg4K2SXqf9LAM 52a/t2HfpY5y49sbrgRb1llP6c8RVWhUX/pGdjbcIM97+1CJEWBN8Vmraoe4+71o 1idTPehJfHRNeyXQUnro8CmnSxE9tLHtdKj0pzvO+yqT66O6Iw1aUAIX+dG4Us9Q Z22ypFHaJ74lKw9JFwAFTJ/TF1rXUXqgufYTNNqP3Ra7wCHF8BmtjwRYAlvsR9CO c4eVC1+qhq/8/EOMCgF/rsbZW93r/nz5xgsSX0k6WkAz5WX2mniHfmBFpmr039jZ 0eI1mEMGDAYuUn05++dNveo/ZOZj3wBlFzyfNSeeWJB5SdKPTvN3H/Iu0Aw+Rtb6 szwNClaFAgMBAAECggEAHZ8cjVRUJ/tiJorzlTyfKZ6hwhsPv4JIRVg6LhnceZWA jPW2cHSWyl2epyx55lhH7iyeeY7vXOqrX1aBMDb1stSWw2dH/tdxYSkqEmksa+R6 fL6kl5RV5epjpPt77Z3VmPq9UbP/M310qKWcgB8lw4wN0AfKMqsZLYauk9BVhNRu Bgah9O7BmcXS+mp49w0Xyfo1UBvzW8R6UnBhHbf9aOY8ObMD0Jj/wDjlYMqSSIKR 9/8GZWQEKe6q0PyRRdNNtdzbpBrR0fIw6/T9pfDR2fBAcpNvD50eJk2jRiRDTWFJ rVSc0bvZFb74Rc3LbMSXW/6Kb7I2IG1XsWw7nxp92QKBgQDgzdIxZrkNZ3Tbuzng SG4atjnaCXoekOHK7VZVYd30S0AAizeGu1sjpUVQgsf+qkFskXAQp2/2f+Wiuq2G +nJYvXwZ/r9IcUs/oD3Fa2ezCVz1N/HOSPFAZK9XZuZbL8sXEYIPGJWH5F8Sanmb xNp9IUynlpwgM2JlZNeTCkv4PQKBgQDMbL/AF3LSpKvwi+QvYVkX/gChQmNMr4pP TM/GI4D03tNrzsut3oerKMUw0c5MxonkAJpuACN6baRyBOBxRYQSt8wWkORg9iqy a7aHnQqIGRafydW1/Snhr2DJSSaViHfO0oaA1r61zgMUTnSGb3UjyxJQp65dvPac BhpR9wpz6QKBgQDR2S/CL8rEqXObfi1roREu3DYqw7f8enBb1qtFrsLbPbd0CoD9 wz0zjB6lJj/9CP9jkmwTD8njR8ab3jkIDBfboJ4NQhFbVW7R6QpglH9L0Iy2189g KhUScCqBoyubqYSidxR6dQ94uATLkxsL/nmaXxBITL5XDMBoN/dIak86XQKBgDqa oo4LKtvAYZpgQFZk7gm2w693PMhrOpdpSddfrkSE7M9nRXTe6r3ivkU0oJPaBwXa Nmt6lrEuZYpaY42VhDtpfZSqjQ5PBAaKYpWWK8LAjn/YeO/nV+5fPLv3wJv1t4MP T4f4CExOdwuHQliX81kDioicyZwN5BTumvUMgW6hAoGAF29kI1KthKaHN9P1DchI qqoHb9FPdZ5I6HDQpn6fr9ut7+9kVqexUrQ2AMvcVei6gDWW6P3yDCdTKcV9qtts 1JOP2aSmXvibflx/bNfnhu988qJDhJ3CCjfc79fjwntUIXNPsFmwC9W5lnlSMKHM rH4RdmnjeCIG1PZ35m/yUSU= -----END PRIVATE KEY-----''' if not os.path.exists(EXPECTED_TS_MON_CONFIG_NAME): try: os.makedirs(os.path.dirname(EXPECTED_TS_MON_CONFIG_NAME)) except OSError: # Directory already exists. pass with open(EXPECTED_TS_MON_CONFIG_NAME, 'w') as f: f.write(FAKE_TS_MON_CONFIG_CONTENT) with open ('/tmp/service_account_prodx_mon.json', 'w') as f: f.write(FAKE_SERVICE_ACCOUNT_CRED_JSON % repr(TEST_KEY)[2:-1]) """ # Name of the test control file. TEST_CONTROL_FILE = 'attach.1' TEST_DUT = '172.27.213.193' TEST_RESULT_PATH = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER # Test autoserv command. AUTOSERV_COMMAND = (('/usr/bin/python -u /usr/local/autotest/server/autoserv ' '-p -r %(result_path)s/%(test_dut)s -m %(test_dut)s ' '-u debug_user -l test -s -P %(job_id)s-debug_user/' '%(test_dut)s -n %(result_path)s/%(test_control_file)s ' '--verify_job_repo_url') % {'job_id': TEST_JOB_ID, 'result_path': TEST_RESULT_PATH, 'test_dut': TEST_DUT, 'test_control_file': TEST_CONTROL_FILE}) # Content of the test control file. TEST_CONTROL_CONTENT = """ def run(machine): job.run_test('dummy_PassServer', host=hosts.create_host(machine)) parallel_simple(run, machines) """ def setup_base(container_path): """Test setup base container works. @param bucket: ContainerBucket to interact with containers. """ logging.info('Rebuild base container in folder %s.', container_path) image = base_image.BaseImage(container_path, lxc.BASE) image.setup() logging.info('Base container created: %s', image.get().name) def setup_test(bucket, container_id, skip_cleanup): """Test container can be created from base container. @param bucket: ContainerBucket to interact with containers. @param container_id: ID of the test container. @param skip_cleanup: Set to True to skip cleanup, used to troubleshoot container failures. @return: A Container object created for the test container. """ logging.info('Create test container.') os.makedirs(RESULT_PATH) container = bucket.setup_test(container_id, TEST_JOB_ID, AUTOTEST_SERVER_PKG, RESULT_PATH, skip_cleanup=skip_cleanup, job_folder=TEST_JOB_FOLDER, dut_name='192.168.0.3') # Inject "AUTOSERV/testing_mode: True" in shadow config to test autoserv. container.attach_run('echo $\'[AUTOSERV]\ntesting_mode: True\' >>' ' /usr/local/autotest/shadow_config.ini') if not utils.is_moblab(): # Create fake '/etc/chrome-infra/ts-mon.json' if it doesn't exist. create_key_script = os.path.join( RESULT_PATH, CREATE_FAKE_TS_MON_CONFIG_SCRIPT) with open(create_key_script, 'w') as script: script.write(CREATE_FAKE_TS_MON_CONFIG_SCRIPT_CONTENT) container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER container_create_key_script = os.path.join( container_result_path, CREATE_FAKE_TS_MON_CONFIG_SCRIPT) container.attach_run('python %s' % container_create_key_script) return container def test_share(container): """Test container can share files with the host. @param container: The test container. """ logging.info('Test files written to result directory can be accessed ' 'from the host running the container..') host_test_script = os.path.join(RESULT_PATH, TEST_SCRIPT) with open(host_test_script, 'w') as script: if utils.is_moblab(): script.write(TEST_SCRIPT_CONTENT % {'ts_mon_test': ''}) else: script.write(TEST_SCRIPT_CONTENT % {'ts_mon_test': TEST_SCRIPT_CONTENT_TS_MON}) container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER container_test_script = os.path.join(container_result_path, TEST_SCRIPT) container_test_script_dest = os.path.join('/usr/local/autotest/utils/', TEST_SCRIPT) container_test_log = os.path.join(container_result_path, TEST_LOG) host_test_log = os.path.join(RESULT_PATH, TEST_LOG) # Move the test script out of result folder as it needs to import common. container.attach_run('mv %s %s' % (container_test_script, container_test_script_dest)) container.attach_run('python %s %s' % (container_test_script_dest, container_test_log)) if not os.path.exists(host_test_log): raise Exception('Results created in container can not be accessed from ' 'the host.') with open(host_test_log, 'r') as log: if log.read() != 'test': raise Exception('Failed to read the content of results in ' 'container.') def test_autoserv(container): """Test container can run autoserv command. @param container: The test container. """ logging.info('Test autoserv command.') logging.info('Create test control file.') host_control_file = os.path.join(RESULT_PATH, TEST_CONTROL_FILE) with open(host_control_file, 'w') as control_file: control_file.write(TEST_CONTROL_CONTENT) logging.info('Run autoserv command.') container.attach_run(AUTOSERV_COMMAND) logging.info('Confirm results are available from host.') # Read status.log to check the content is not empty. container_status_log = os.path.join(TEST_RESULT_PATH, TEST_DUT, 'status.log') status_log = container.attach_run(command='cat %s' % container_status_log ).stdout if len(status_log) < 10: raise Exception('Failed to read status.log in container.') def test_package_install(container): """Test installing package in container. @param container: The test container. """ # Packages are installed in TEST_SCRIPT_CONTENT. Verify the packages in # this method. container.attach_run('which atop') container.attach_run('python -c "import acora"') def test_ssh(container, remote): """Test container can run ssh to remote server. @param container: The test container. @param remote: The remote server to ssh to. @raise: error.CmdError if container can't ssh to remote server. """ logging.info('Test ssh to %s.', remote) container.attach_run('ssh %s -a -x -o StrictHostKeyChecking=no ' '-o BatchMode=yes -o UserKnownHostsFile=/dev/null ' '-p 22 "true"' % remote) def parse_options(): """Parse command line inputs. """ parser = argparse.ArgumentParser() parser.add_argument('-d', '--dut', type=str, help='Test device to ssh to.', default=None) parser.add_argument('-r', '--devserver', type=str, help='Test devserver to ssh to.', default=None) parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Print out ALL entries.') parser.add_argument('-s', '--skip_cleanup', action='store_true', default=False, help='Skip deleting test containers.') return parser.parse_args() def main(options): """main script. @param options: Options to run the script. """ # Verify that the test is running as the correct user. unittest_setup.verify_user() log_level = (logging.DEBUG if options.verbose else logging.INFO) unittest_setup.setup_logging(log_level) setup_base(TEMP_DIR) bucket = lxc.ContainerBucket(TEMP_DIR) container_id = lxc.ContainerId.create(TEST_JOB_ID) container = setup_test(bucket, container_id, options.skip_cleanup) test_share(container) test_autoserv(container) if options.dut: test_ssh(container, options.dut) if options.devserver: test_ssh(container, options.devserver) # Packages are installed in TEST_SCRIPT, verify the packages are installed. test_package_install(container) logging.info('All tests passed.') if __name__ == '__main__': options = parse_options() try: main(options) except: # If the cleanup code below raises additional errors, they obfuscate the # actual error in the test. Highlight the error to aid in debugging. logging.exception('ERROR:\n%s', error.format_error()) raise finally: if not options.skip_cleanup: logging.info('Cleaning up temporary directory %s.', TEMP_DIR) try: lxc.ContainerBucket(TEMP_DIR).destroy_all() finally: utils.run('sudo rm -rf "%s"' % TEMP_DIR)