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