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