1#!/usr/bin/env 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 6import collections 7import json 8import os 9import tempfile 10import unittest 11from contextlib import contextmanager 12 13import common 14from autotest_lib.client.bin import utils 15from autotest_lib.site_utils.lxc import config as lxc_config 16from autotest_lib.site_utils.lxc import utils as lxc_utils 17 18class DeployConfigTest(unittest.TestCase): 19 """Test DeployConfigManager. 20 """ 21 22 def testValidate(self): 23 """Test ssp_deploy_config.json can be validated. 24 """ 25 global_deploy_config_file = os.path.join( 26 common.autotest_dir, lxc_config.SSP_DEPLOY_CONFIG_FILE) 27 with open(global_deploy_config_file) as f: 28 deploy_configs = json.load(f) 29 for config in deploy_configs: 30 if 'append' in config: 31 lxc_config.DeployConfigManager.validate(config) 32 elif 'mount' in config: 33 # validate_mount checks that the path exists, so we can't call 34 # it from tests. 35 pass 36 else: 37 self.fail('Non-deploy/mount config %s' % config) 38 39 40 def testPreStart(self): 41 """Verifies that pre-start works correctly. 42 Checks that mounts are correctly created in the container. 43 """ 44 with lxc_utils.TempDir() as tmpdir: 45 config = [ 46 { 47 'mount': True, 48 'source': tempfile.mkdtemp(dir=tmpdir), 49 'target': '/target0', 50 'readonly': True, 51 'force_create': False 52 }, 53 { 54 'mount': True, 55 'source': tempfile.mkdtemp(dir=tmpdir), 56 'target': '/target1', 57 'readonly': False, 58 'force_create': False 59 }, 60 ] 61 with ConfigFile(config) as test_cfg, MockContainer() as container: 62 manager = lxc_config.DeployConfigManager(container, test_cfg) 63 manager.deploy_pre_start() 64 self.assertEqual(len(config), len(container.mounts)) 65 for c in config: 66 self.assertTrue(container.has_mount(c)) 67 68 69 def testPreStartWithCreate(self): 70 """Verifies that pre-start creates mounted dirs. 71 72 Checks that missing mount points are created when force_create is 73 enabled. 74 """ 75 with lxc_utils.TempDir() as tmpdir: 76 src_dir = os.path.join(tmpdir, 'foobar') 77 config = [{ 78 'mount': True, 79 'source': src_dir, 80 'target': '/target0', 81 'readonly': True, 82 'force_create': True 83 }] 84 with ConfigFile(config) as test_cfg, MockContainer() as container: 85 manager = lxc_config.DeployConfigManager(container, test_cfg) 86 # Pre-condition: the path doesn't exist. 87 self.assertFalse(lxc_utils.path_exists(src_dir)) 88 89 # After calling deploy_pre_start, the path should exist and the 90 # mount should be created in the container. 91 manager.deploy_pre_start() 92 self.assertTrue(lxc_utils.path_exists(src_dir)) 93 self.assertEqual(len(config), len(container.mounts)) 94 for c in config: 95 self.assertTrue(container.has_mount(c)) 96 97 98class _MockContainer(object): 99 """A test mock for the container class. 100 101 Don't instantiate this directly, use the MockContainer context manager 102 defined below. 103 """ 104 105 def __init__(self): 106 self.rootfs = tempfile.mkdtemp() 107 self.mounts = [] 108 self.MountConfig = collections.namedtuple( 109 'MountConfig', ['source', 'destination', 'readonly']) 110 111 112 def cleanup(self): 113 """Clean up tmp dirs created by the container.""" 114 # DeployConfigManager uses sudo to create some directories in the 115 # container, so it's necessary to use sudo to clean up. 116 utils.run('sudo rm -rf %s' % self.rootfs) 117 118 119 def mount_dir(self, src, dst, ro): 120 """Stub implementation of mount_dir. 121 122 Records calls for later verification. 123 124 @param src: Mount source dir. 125 @param dst: Mount destination dir. 126 @param ro: Read-only flag. 127 """ 128 self.mounts.append(self.MountConfig(src, dst, ro)) 129 130 131 def has_mount(self, config): 132 """Verifies whether an earlier call was made to mount_dir. 133 134 @param config: The config object to verify. 135 136 @return True if an earlier call was made to mount_dir that matches the 137 given mount configuration; False otherwise. 138 """ 139 mount = self.MountConfig(config['source'], 140 config['target'], 141 config['readonly']) 142 return mount in self.mounts 143 144 145@contextmanager 146def MockContainer(): 147 """Context manager for creating a _MockContainer for testing.""" 148 container = _MockContainer() 149 try: 150 yield container 151 finally: 152 container.cleanup() 153 154 155@contextmanager 156def ConfigFile(config): 157 """Context manager for creating a config file. 158 159 The given configs are translated into json and pushed into a temporary file 160 that the DeployConfigManager can read. 161 162 @param config: A list of config objects. Each config object is a dictionary 163 which conforms to the format described in config.py. 164 """ 165 with tempfile.NamedTemporaryFile() as tmp: 166 json.dump(config, tmp) 167 tmp.flush() 168 yield tmp.name 169 170 171if __name__ == '__main__': 172 unittest.main() 173