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