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