1#!/usr/bin/python2 2# Copyright 2015 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Function tests of lxc module. To be able to run this test, following setup 7is required: 8 1. lxc is installed. 9 2. Autotest code exists in /usr/local/autotest, with site-packages installed. 10 (run utils/build_externals.py) 11 3. The user runs the test should have sudo access. Run the test with sudo. 12Note that the test does not require Autotest database and frontend. 13""" 14 15 16import argparse 17import logging 18import os 19import tempfile 20 21import common 22from autotest_lib.client.bin import utils 23from autotest_lib.client.common_lib import error 24from autotest_lib.site_utils import lxc 25from autotest_lib.site_utils.lxc import base_image 26from autotest_lib.site_utils.lxc import unittest_setup 27 28 29TEST_JOB_ID = 123 30TEST_JOB_FOLDER = '123-debug_user' 31# Create a temp directory for functional tests. The directory is not under /tmp 32# for Moblab to be able to run the test. 33# But first, ensure that the containing directory exists: 34 35if not os.path.exists(lxc.DEFAULT_CONTAINER_PATH): 36 os.makedirs(lxc.DEFAULT_CONTAINER_PATH) 37TEMP_DIR = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH, 38 prefix='container_test_') 39RESULT_PATH = os.path.join(TEMP_DIR, 'results', str(TEST_JOB_ID)) 40# Link to download a test package of autotest server package. 41# Ideally the test should stage a build on devserver and download the 42# autotest_server_package from devserver. This test is focused on testing 43# container, so it's prefered to avoid dependency on devserver. 44AUTOTEST_SERVER_PKG = ('http://storage.googleapis.com/abci-ssp/' 45 'autotest-containers/autotest_server_package.tar.bz2') 46 47# Test log file to be created in result folder, content is `test`. 48TEST_LOG = 'test.log' 49# Name of test script file to run in container. 50TEST_SCRIPT = 'test.py' 51# Test script to run in container to verify autotest code setup. 52TEST_SCRIPT_CONTENT = """ 53import socket 54import sys 55 56# Test import 57import common 58import chromite 59 60# This test has to be before the import of autotest_lib, because ts_mon requires 61# httplib2 module in chromite/third_party. The one in Autotest site-packages is 62# out dated. 63%(ts_mon_test)s 64 65from autotest_lib.server import utils 66from autotest_lib.site_utils import lxc 67 68with open(sys.argv[1], 'w') as f: 69 f.write('test') 70 71# Confirm hostname starts with `test-` 72if not socket.gethostname().startswith('test-'): 73 raise Exception('The container\\\'s hostname must start with `test-`.') 74 75# Test installing packages 76lxc.install_packages(['atop'], ['acora']) 77 78""" 79 80TEST_SCRIPT_CONTENT_TS_MON = """ 81# Test ts_mon metrics can be set up. 82from chromite.lib import ts_mon_config 83ts_mon_config.SetupTsMonGlobalState('some_test', suppress_exception=False) 84""" 85 86CREATE_FAKE_TS_MON_CONFIG_SCRIPT = 'create_fake_key.py' 87 88CREATE_FAKE_TS_MON_CONFIG_SCRIPT_CONTENT = """ 89import os 90import rsa 91 92EXPECTED_TS_MON_CONFIG_NAME = '/etc/chrome-infra/ts-mon.json' 93 94FAKE_TS_MON_CONFIG_CONTENT = ''' 95 { 96 "credentials":"/tmp/service_account_prodx_mon.json", 97 "endpoint":"https://xxx.googleapis.com/v1:insert", 98 "use_new_proto": true 99 }''' 100 101FAKE_SERVICE_ACCOUNT_CRED_JSON = ''' 102 { 103 "type": "service_account", 104 "project_id": "test_project", 105 "private_key_id": "aaa", 106 "private_key": "%s", 107 "client_email": "xxx", 108 "client_id": "111", 109 "auth_uri": "https://accounts.google.com/o/oauth2/auth", 110 "token_uri": "https://accounts.google.com/o/oauth2/token", 111 "auth_provider_x509_cert_url": 112 "https://www.googleapis.com/oauth2/v1/certs", 113 "client_x509_cert_url": 114 "https://www.googleapis.com/robot/v1/metadata/x509/xxx" 115 }''' 116 117 118TEST_KEY = '''------BEGIN PRIVATE KEY----- 119MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzg4K2SXqf9LAM 12052a/t2HfpY5y49sbrgRb1llP6c8RVWhUX/pGdjbcIM97+1CJEWBN8Vmraoe4+71o 1211idTPehJfHRNeyXQUnro8CmnSxE9tLHtdKj0pzvO+yqT66O6Iw1aUAIX+dG4Us9Q 122Z22ypFHaJ74lKw9JFwAFTJ/TF1rXUXqgufYTNNqP3Ra7wCHF8BmtjwRYAlvsR9CO 123c4eVC1+qhq/8/EOMCgF/rsbZW93r/nz5xgsSX0k6WkAz5WX2mniHfmBFpmr039jZ 1240eI1mEMGDAYuUn05++dNveo/ZOZj3wBlFzyfNSeeWJB5SdKPTvN3H/Iu0Aw+Rtb6 125szwNClaFAgMBAAECggEAHZ8cjVRUJ/tiJorzlTyfKZ6hwhsPv4JIRVg6LhnceZWA 126jPW2cHSWyl2epyx55lhH7iyeeY7vXOqrX1aBMDb1stSWw2dH/tdxYSkqEmksa+R6 127fL6kl5RV5epjpPt77Z3VmPq9UbP/M310qKWcgB8lw4wN0AfKMqsZLYauk9BVhNRu 128Bgah9O7BmcXS+mp49w0Xyfo1UBvzW8R6UnBhHbf9aOY8ObMD0Jj/wDjlYMqSSIKR 1299/8GZWQEKe6q0PyRRdNNtdzbpBrR0fIw6/T9pfDR2fBAcpNvD50eJk2jRiRDTWFJ 130rVSc0bvZFb74Rc3LbMSXW/6Kb7I2IG1XsWw7nxp92QKBgQDgzdIxZrkNZ3Tbuzng 131SG4atjnaCXoekOHK7VZVYd30S0AAizeGu1sjpUVQgsf+qkFskXAQp2/2f+Wiuq2G 132+nJYvXwZ/r9IcUs/oD3Fa2ezCVz1N/HOSPFAZK9XZuZbL8sXEYIPGJWH5F8Sanmb 133xNp9IUynlpwgM2JlZNeTCkv4PQKBgQDMbL/AF3LSpKvwi+QvYVkX/gChQmNMr4pP 134TM/GI4D03tNrzsut3oerKMUw0c5MxonkAJpuACN6baRyBOBxRYQSt8wWkORg9iqy 135a7aHnQqIGRafydW1/Snhr2DJSSaViHfO0oaA1r61zgMUTnSGb3UjyxJQp65dvPac 136BhpR9wpz6QKBgQDR2S/CL8rEqXObfi1roREu3DYqw7f8enBb1qtFrsLbPbd0CoD9 137wz0zjB6lJj/9CP9jkmwTD8njR8ab3jkIDBfboJ4NQhFbVW7R6QpglH9L0Iy2189g 138KhUScCqBoyubqYSidxR6dQ94uATLkxsL/nmaXxBITL5XDMBoN/dIak86XQKBgDqa 139oo4LKtvAYZpgQFZk7gm2w693PMhrOpdpSddfrkSE7M9nRXTe6r3ivkU0oJPaBwXa 140Nmt6lrEuZYpaY42VhDtpfZSqjQ5PBAaKYpWWK8LAjn/YeO/nV+5fPLv3wJv1t4MP 141T4f4CExOdwuHQliX81kDioicyZwN5BTumvUMgW6hAoGAF29kI1KthKaHN9P1DchI 142qqoHb9FPdZ5I6HDQpn6fr9ut7+9kVqexUrQ2AMvcVei6gDWW6P3yDCdTKcV9qtts 1431JOP2aSmXvibflx/bNfnhu988qJDhJ3CCjfc79fjwntUIXNPsFmwC9W5lnlSMKHM 144rH4RdmnjeCIG1PZ35m/yUSU= 145-----END PRIVATE KEY-----''' 146 147if not os.path.exists(EXPECTED_TS_MON_CONFIG_NAME): 148 try: 149 os.makedirs(os.path.dirname(EXPECTED_TS_MON_CONFIG_NAME)) 150 except OSError: 151 # Directory already exists. 152 pass 153 154 with open(EXPECTED_TS_MON_CONFIG_NAME, 'w') as f: 155 f.write(FAKE_TS_MON_CONFIG_CONTENT) 156 with open ('/tmp/service_account_prodx_mon.json', 'w') as f: 157 f.write(FAKE_SERVICE_ACCOUNT_CRED_JSON % repr(TEST_KEY)[2:-1]) 158""" 159 160# Name of the test control file. 161TEST_CONTROL_FILE = 'attach.1' 162TEST_DUT = '172.27.213.193' 163TEST_RESULT_PATH = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER 164# Test autoserv command. 165AUTOSERV_COMMAND = (('/usr/bin/python -u /usr/local/autotest/server/autoserv ' 166 '-p -r %(result_path)s/%(test_dut)s -m %(test_dut)s ' 167 '-u debug_user -l test -s -P %(job_id)s-debug_user/' 168 '%(test_dut)s -n %(result_path)s/%(test_control_file)s ' 169 '--verify_job_repo_url') % 170 {'job_id': TEST_JOB_ID, 171 'result_path': TEST_RESULT_PATH, 172 'test_dut': TEST_DUT, 173 'test_control_file': TEST_CONTROL_FILE}) 174# Content of the test control file. 175TEST_CONTROL_CONTENT = """ 176def run(machine): 177 job.run_test('dummy_PassServer', 178 host=hosts.create_host(machine)) 179 180parallel_simple(run, machines) 181""" 182 183 184def setup_base(container_path): 185 """Test setup base container works. 186 187 @param bucket: ContainerBucket to interact with containers. 188 """ 189 logging.info('Rebuild base container in folder %s.', container_path) 190 image = base_image.BaseImage(container_path, lxc.BASE) 191 image.setup() 192 logging.info('Base container created: %s', image.get().name) 193 194 195def setup_test(bucket, container_id, skip_cleanup): 196 """Test container can be created from base container. 197 198 @param bucket: ContainerBucket to interact with containers. 199 @param container_id: ID of the test container. 200 @param skip_cleanup: Set to True to skip cleanup, used to troubleshoot 201 container failures. 202 203 @return: A Container object created for the test container. 204 """ 205 logging.info('Create test container.') 206 os.makedirs(RESULT_PATH) 207 container = bucket.setup_test(container_id, TEST_JOB_ID, 208 AUTOTEST_SERVER_PKG, RESULT_PATH, 209 skip_cleanup=skip_cleanup, 210 job_folder=TEST_JOB_FOLDER, 211 dut_name='192.168.0.3') 212 213 # Inject "AUTOSERV/testing_mode: True" in shadow config to test autoserv. 214 container.attach_run('echo $\'[AUTOSERV]\ntesting_mode: True\' >>' 215 ' /usr/local/autotest/shadow_config.ini') 216 217 if not utils.is_moblab(): 218 # Create fake '/etc/chrome-infra/ts-mon.json' if it doesn't exist. 219 create_key_script = os.path.join( 220 RESULT_PATH, CREATE_FAKE_TS_MON_CONFIG_SCRIPT) 221 with open(create_key_script, 'w') as script: 222 script.write(CREATE_FAKE_TS_MON_CONFIG_SCRIPT_CONTENT) 223 container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER 224 container_create_key_script = os.path.join( 225 container_result_path, CREATE_FAKE_TS_MON_CONFIG_SCRIPT) 226 container.attach_run('python %s' % container_create_key_script) 227 228 return container 229 230 231def test_share(container): 232 """Test container can share files with the host. 233 234 @param container: The test container. 235 """ 236 logging.info('Test files written to result directory can be accessed ' 237 'from the host running the container..') 238 host_test_script = os.path.join(RESULT_PATH, TEST_SCRIPT) 239 with open(host_test_script, 'w') as script: 240 if utils.is_moblab(): 241 script.write(TEST_SCRIPT_CONTENT % {'ts_mon_test': ''}) 242 else: 243 script.write(TEST_SCRIPT_CONTENT % 244 {'ts_mon_test': TEST_SCRIPT_CONTENT_TS_MON}) 245 246 container_result_path = lxc.RESULT_DIR_FMT % TEST_JOB_FOLDER 247 container_test_script = os.path.join(container_result_path, TEST_SCRIPT) 248 container_test_script_dest = os.path.join('/usr/local/autotest/utils/', 249 TEST_SCRIPT) 250 container_test_log = os.path.join(container_result_path, TEST_LOG) 251 host_test_log = os.path.join(RESULT_PATH, TEST_LOG) 252 # Move the test script out of result folder as it needs to import common. 253 container.attach_run('mv %s %s' % (container_test_script, 254 container_test_script_dest)) 255 container.attach_run('python %s %s' % (container_test_script_dest, 256 container_test_log)) 257 if not os.path.exists(host_test_log): 258 raise Exception('Results created in container can not be accessed from ' 259 'the host.') 260 with open(host_test_log, 'r') as log: 261 if log.read() != 'test': 262 raise Exception('Failed to read the content of results in ' 263 'container.') 264 265 266def test_autoserv(container): 267 """Test container can run autoserv command. 268 269 @param container: The test container. 270 """ 271 logging.info('Test autoserv command.') 272 logging.info('Create test control file.') 273 host_control_file = os.path.join(RESULT_PATH, TEST_CONTROL_FILE) 274 with open(host_control_file, 'w') as control_file: 275 control_file.write(TEST_CONTROL_CONTENT) 276 277 logging.info('Run autoserv command.') 278 container.attach_run(AUTOSERV_COMMAND) 279 280 logging.info('Confirm results are available from host.') 281 # Read status.log to check the content is not empty. 282 container_status_log = os.path.join(TEST_RESULT_PATH, TEST_DUT, 283 'status.log') 284 status_log = container.attach_run(command='cat %s' % container_status_log 285 ).stdout 286 if len(status_log) < 10: 287 raise Exception('Failed to read status.log in container.') 288 289 290def test_package_install(container): 291 """Test installing package in container. 292 293 @param container: The test container. 294 """ 295 # Packages are installed in TEST_SCRIPT_CONTENT. Verify the packages in 296 # this method. 297 container.attach_run('which atop') 298 container.attach_run('python -c "import acora"') 299 300 301def test_ssh(container, remote): 302 """Test container can run ssh to remote server. 303 304 @param container: The test container. 305 @param remote: The remote server to ssh to. 306 307 @raise: error.CmdError if container can't ssh to remote server. 308 """ 309 logging.info('Test ssh to %s.', remote) 310 container.attach_run('ssh %s -a -x -o StrictHostKeyChecking=no ' 311 '-o BatchMode=yes -o UserKnownHostsFile=/dev/null ' 312 '-p 22 "true"' % remote) 313 314 315def parse_options(): 316 """Parse command line inputs. 317 """ 318 parser = argparse.ArgumentParser() 319 parser.add_argument('-d', '--dut', type=str, 320 help='Test device to ssh to.', 321 default=None) 322 parser.add_argument('-r', '--devserver', type=str, 323 help='Test devserver to ssh to.', 324 default=None) 325 parser.add_argument('-v', '--verbose', action='store_true', 326 default=False, 327 help='Print out ALL entries.') 328 parser.add_argument('-s', '--skip_cleanup', action='store_true', 329 default=False, 330 help='Skip deleting test containers.') 331 return parser.parse_args() 332 333 334def main(options): 335 """main script. 336 337 @param options: Options to run the script. 338 """ 339 # Verify that the test is running as the correct user. 340 unittest_setup.verify_user() 341 342 log_level = (logging.DEBUG if options.verbose else logging.INFO) 343 unittest_setup.setup_logging(log_level) 344 345 setup_base(TEMP_DIR) 346 bucket = lxc.ContainerBucket(TEMP_DIR) 347 348 container_id = lxc.ContainerId.create(TEST_JOB_ID) 349 container = setup_test(bucket, container_id, options.skip_cleanup) 350 test_share(container) 351 test_autoserv(container) 352 if options.dut: 353 test_ssh(container, options.dut) 354 if options.devserver: 355 test_ssh(container, options.devserver) 356 # Packages are installed in TEST_SCRIPT, verify the packages are installed. 357 test_package_install(container) 358 logging.info('All tests passed.') 359 360 361if __name__ == '__main__': 362 options = parse_options() 363 try: 364 main(options) 365 except: 366 # If the cleanup code below raises additional errors, they obfuscate the 367 # actual error in the test. Highlight the error to aid in debugging. 368 logging.exception('ERROR:\n%s', error.format_error()) 369 raise 370 finally: 371 if not options.skip_cleanup: 372 logging.info('Cleaning up temporary directory %s.', TEMP_DIR) 373 try: 374 lxc.ContainerBucket(TEMP_DIR).destroy_all() 375 finally: 376 utils.run('sudo rm -rf "%s"' % TEMP_DIR) 377