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