blob: 9d517855cfbdcbdc2c0e46b7e2debae4132ab405 [file] [log] [blame]
Dan Shi767dced2015-02-01 00:21:07 -08001# 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
Dan Shi7836d252015-04-27 15:33:58 -070023from autotest_lib.client.bin import utils
Dan Shi767dced2015-02-01 00:21:07 -080024from autotest_lib.site_utils import lxc
25
26
27TEST_JOB_ID = 123
Dan Shiafa63872016-02-23 15:32:31 -080028TEST_JOB_FOLDER = '123-debug_user'
Dan Shi767dced2015-02-01 00:21:07 -080029# Create a temp directory for functional tests. The directory is not under /tmp
30# for Moblab to be able to run the test.
31TEMP_DIR = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH,
32 prefix='container_test_')
33RESULT_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 Shi9d41c872016-08-02 15:17:28 -070038AUTOTEST_SERVER_PKG = ('http://storage.googleapis.com/abci-ssp/'
Dan Shi767dced2015-02-01 00:21:07 -080039 'autotest-containers/autotest_server_package.tar.bz2')
40
41# Test log file to be created in result folder, content is `test`.
42TEST_LOG = 'test.log'
43# Name of test script file to run in container.
44TEST_SCRIPT = 'test.py'
45# Test script to run in container to verify autotest code setup.
46TEST_SCRIPT_CONTENT = """
Dan Shi2f55dc22016-08-31 21:47:34 -070047import socket
Dan Shi767dced2015-02-01 00:21:07 -080048import sys
49
50# Test import
51import common
52import chromite
53from autotest_lib.server import utils
Dan Shid9094d42015-07-09 18:09:22 -070054from autotest_lib.site_utils import lxc
Dan Shi767dced2015-02-01 00:21:07 -080055
56with open(sys.argv[1], 'w') as f:
57 f.write('test')
Dan Shid9094d42015-07-09 18:09:22 -070058
Dan Shi2f55dc22016-08-31 21:47:34 -070059# Confirm hostname starts with `test_`
60if not socket.gethostname().startswith('test_'):
61 raise Exception('The container\\\'s hostname must start with `test_`.')
62
Dan Shid9094d42015-07-09 18:09:22 -070063# Test installing packages
64lxc.install_packages(['atop', 'libxslt-dev'], ['selenium', 'numpy'])
65
Dan Shi767dced2015-02-01 00:21:07 -080066"""
67# Name of the test control file.
68TEST_CONTROL_FILE = 'attach.1'
69TEST_DUT = '172.27.213.193'
Dan Shiafa63872016-02-23 15:32:31 -080070TEST_RESULT_PATH = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER
Dan Shi767dced2015-02-01 00:21:07 -080071# Test autoserv command.
72AUTOSERV_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.
82TEST_CONTROL_CONTENT = """
83def run(machine):
84 job.run_test('dummy_PassServer',
85 host=hosts.create_host(machine))
86
87parallel_simple(run, machines)
88"""
89
90
91def 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
106def 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
117def 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,
Dan Shiafa63872016-02-23 15:32:31 -0800130 RESULT_PATH, skip_cleanup=skip_cleanup,
Dan Shi2f55dc22016-08-31 21:47:34 -0700131 job_folder=TEST_JOB_FOLDER,
132 dut_name='192.168.0.3')
Dan Shi767dced2015-02-01 00:21:07 -0800133
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
140def 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
Dan Shiafa63872016-02-23 15:32:31 -0800151 container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER
Dan Shi767dced2015-02-01 00:21:07 -0800152 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
171def 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
Dan Shi507fdc42015-04-30 10:59:37 -0700195def test_package_install(container):
196 """Test installing package in container.
197
198 @param container: The test container.
199 """
Dan Shid9094d42015-07-09 18:09:22 -0700200 # 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"')
Dan Shi507fdc42015-04-30 10:59:37 -0700204
205
Dan Shi7836d252015-04-27 15:33:58 -0700206def 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
Dan Shi767dced2015-02-01 00:21:07 -0800220def parse_options():
221 """Parse command line inputs.
222 """
223 parser = argparse.ArgumentParser()
Dan Shi7836d252015-04-27 15:33:58 -0700224 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)
Dan Shi767dced2015-02-01 00:21:07 -0800230 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
239def 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.
Dan Shica3be482015-05-05 23:23:53 -0700247 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')
Dan Shi767dced2015-02-01 00:21:07 -0800251
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 %
Dan Shid68d51c2015-04-21 17:00:42 -0700259 (TEST_JOB_ID, time.time(), os.getpid()))
Dan Shi767dced2015-02-01 00:21:07 -0800260 container = setup_test(bucket, container_test_name, options.skip_cleanup)
261 test_share(container)
262 test_autoserv(container)
Dan Shi7836d252015-04-27 15:33:58 -0700263 if options.dut:
264 test_ssh(container, options.dut)
265 if options.devserver:
266 test_ssh(container, options.devserver)
Dan Shid9094d42015-07-09 18:09:22 -0700267 # Packages are installed in TEST_SCRIPT, verify the packages are installed.
Dan Shi507fdc42015-04-30 10:59:37 -0700268 test_package_install(container)
Dan Shi767dced2015-02-01 00:21:07 -0800269 logging.info('All tests passed.')
270
271
272if __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:
Dan Shi7836d252015-04-27 15:33:58 -0700282 utils.run('sudo rm -rf "%s"' % TEMP_DIR)