xref: /aosp_15_r20/external/autotest/site_utils/lxc/config.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright 2015 The Chromium Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li
5*9c5db199SXin Li"""
6*9c5db199SXin LiThis module helps to deploy config files and shared folders from host to
7*9c5db199SXin Licontainer. It reads the settings from a setting file (ssp_deploy_config), and
8*9c5db199SXin Lideploy the config files based on the settings. The setting file has a json
9*9c5db199SXin Listring of a list of deployment settings. For example:
10*9c5db199SXin Li[{
11*9c5db199SXin Li    "source": "/etc/resolv.conf",
12*9c5db199SXin Li    "target": "/etc/resolv.conf",
13*9c5db199SXin Li    "append": true,
14*9c5db199SXin Li    "permission": 400
15*9c5db199SXin Li },
16*9c5db199SXin Li {
17*9c5db199SXin Li    "source": "ssh",
18*9c5db199SXin Li    "target": "/root/.ssh",
19*9c5db199SXin Li    "append": false,
20*9c5db199SXin Li    "permission": 400
21*9c5db199SXin Li },
22*9c5db199SXin Li {
23*9c5db199SXin Li    "source": "/usr/local/autotest/results/shared",
24*9c5db199SXin Li    "target": "/usr/local/autotest/results/shared",
25*9c5db199SXin Li    "mount": true,
26*9c5db199SXin Li    "readonly": false,
27*9c5db199SXin Li    "force_create": true
28*9c5db199SXin Li }
29*9c5db199SXin Li]
30*9c5db199SXin Li
31*9c5db199SXin LiDefinition of each attribute for config files are as follows:
32*9c5db199SXin Lisource: config file in host to be copied to container.
33*9c5db199SXin Litarget: config file's location inside container.
34*9c5db199SXin Liappend: true to append the content of config file to existing file inside
35*9c5db199SXin Li        container. If it's set to false, the existing file inside container will
36*9c5db199SXin Li        be overwritten.
37*9c5db199SXin Lipermission: Permission to set to the config file inside container.
38*9c5db199SXin Li
39*9c5db199SXin LiExample:
40*9c5db199SXin Li{
41*9c5db199SXin Li    "source": "/etc/resolv.conf",
42*9c5db199SXin Li    "target": "/etc/resolv.conf",
43*9c5db199SXin Li    "append": true,
44*9c5db199SXin Li    "permission": 400
45*9c5db199SXin Li}
46*9c5db199SXin LiThe above example will:
47*9c5db199SXin Li1. Append the content of /etc/resolv.conf in host machine to file
48*9c5db199SXin Li   /etc/resolv.conf inside container.
49*9c5db199SXin Li2. Copy all files in ssh to /root/.ssh in container.
50*9c5db199SXin Li3. Change all these files' permission to 400
51*9c5db199SXin Li
52*9c5db199SXin LiDefinition of each attribute for sharing folders are as follows:
53*9c5db199SXin Lisource: a folder in host to be mounted in container.
54*9c5db199SXin Litarget: the folder's location inside container.
55*9c5db199SXin Limount: true to mount the source folder onto the target inside container.
56*9c5db199SXin Li       A setting with false value of mount is invalid.
57*9c5db199SXin Lireadonly: true if the mounted folder inside container should be readonly.
58*9c5db199SXin Liforce_create: true to create the source folder if it doesn't exist.
59*9c5db199SXin Li
60*9c5db199SXin LiExample:
61*9c5db199SXin Li {
62*9c5db199SXin Li    "source": "/usr/local/autotest/results/shared",
63*9c5db199SXin Li    "target": "/usr/local/autotest/results/shared",
64*9c5db199SXin Li    "mount": true,
65*9c5db199SXin Li    "readonly": false,
66*9c5db199SXin Li    "force_create": true
67*9c5db199SXin Li }
68*9c5db199SXin LiThe above example will mount folder "/usr/local/autotest/results/shared" in the
69*9c5db199SXin Lihost to path "/usr/local/autotest/results/shared" inside the container. The
70*9c5db199SXin Lifolder can be written to inside container. If the source folder doesn't exist,
71*9c5db199SXin Liit will be created as `force_create` is set to true.
72*9c5db199SXin Li
73*9c5db199SXin LiThe setting file (ssp_deploy_config) lives in AUTOTEST_DIR folder.
74*9c5db199SXin LiFor relative file path specified in ssp_deploy_config, AUTOTEST_DIR/containers
75*9c5db199SXin Liis the parent folder.
76*9c5db199SXin LiThe setting file can be overridden by a shadow config, ssp_deploy_shadow_config.
77*9c5db199SXin LiFor lab servers, puppet should be used to deploy ssp_deploy_shadow_config to
78*9c5db199SXin LiAUTOTEST_DIR and the configure files to AUTOTEST_DIR/containers.
79*9c5db199SXin Li
80*9c5db199SXin LiThe default setting file (ssp_deploy_config) contains
81*9c5db199SXin LiFor SSP to work with none-lab servers, e.g., moblab and developer's workstation,
82*9c5db199SXin Lithe module still supports copy over files like ssh config and autotest
83*9c5db199SXin Lishadow_config to container when AUTOTEST_DIR/containers/ssp_deploy_config is not
84*9c5db199SXin Lipresented.
85*9c5db199SXin Li
86*9c5db199SXin Li"""
87*9c5db199SXin Li
88*9c5db199SXin Liimport collections
89*9c5db199SXin Liimport getpass
90*9c5db199SXin Liimport json
91*9c5db199SXin Liimport os
92*9c5db199SXin Liimport socket
93*9c5db199SXin Li
94*9c5db199SXin Liimport common
95*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config
96*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
97*9c5db199SXin Lifrom autotest_lib.site_utils.lxc import constants
98*9c5db199SXin Lifrom autotest_lib.site_utils.lxc import utils as lxc_utils
99*9c5db199SXin Li
100*9c5db199SXin Li
101*9c5db199SXin Liconfig = global_config.global_config
102*9c5db199SXin Li
103*9c5db199SXin Li# Path to ssp_deploy_config and ssp_deploy_shadow_config.
104*9c5db199SXin LiSSP_DEPLOY_CONFIG_FILE = os.path.join(common.autotest_dir,
105*9c5db199SXin Li                                      'ssp_deploy_config.json')
106*9c5db199SXin LiSSP_DEPLOY_SHADOW_CONFIG_FILE = os.path.join(common.autotest_dir,
107*9c5db199SXin Li                                             'ssp_deploy_shadow_config.json')
108*9c5db199SXin Li# A temp folder used to store files to be appended to the files inside
109*9c5db199SXin Li# container.
110*9c5db199SXin Li_APPEND_FOLDER = '/usr/local/ssp_append'
111*9c5db199SXin Li
112*9c5db199SXin LiDeployConfig = collections.namedtuple(
113*9c5db199SXin Li        'DeployConfig', ['source', 'target', 'append', 'permission'])
114*9c5db199SXin LiMountConfig = collections.namedtuple(
115*9c5db199SXin Li        'MountConfig', ['source', 'target', 'mount', 'readonly',
116*9c5db199SXin Li                        'force_create'])
117*9c5db199SXin Li
118*9c5db199SXin Li
119*9c5db199SXin Liclass SSPDeployError(Exception):
120*9c5db199SXin Li    """Exception raised if any error occurs when setting up test container."""
121*9c5db199SXin Li
122*9c5db199SXin Li
123*9c5db199SXin Liclass DeployConfigManager(object):
124*9c5db199SXin Li    """An object to deploy config to container.
125*9c5db199SXin Li
126*9c5db199SXin Li    The manager retrieves deploy configs from ssp_deploy_config or
127*9c5db199SXin Li    ssp_deploy_shadow_config, and sets up the container accordingly.
128*9c5db199SXin Li    For example:
129*9c5db199SXin Li    1. Copy given config files to specified location inside container.
130*9c5db199SXin Li    2. Append the content of given config files to specific files inside
131*9c5db199SXin Li       container.
132*9c5db199SXin Li    3. Make sure the config files have proper permission inside container.
133*9c5db199SXin Li
134*9c5db199SXin Li    """
135*9c5db199SXin Li
136*9c5db199SXin Li    @staticmethod
137*9c5db199SXin Li    def validate_path(deploy_config):
138*9c5db199SXin Li        """Validate the source and target in deploy_config dict.
139*9c5db199SXin Li
140*9c5db199SXin Li        @param deploy_config: A dictionary of deploy config to be validated.
141*9c5db199SXin Li
142*9c5db199SXin Li        @raise SSPDeployError: If any path in deploy config is invalid.
143*9c5db199SXin Li        """
144*9c5db199SXin Li        target = deploy_config['target']
145*9c5db199SXin Li        source = deploy_config['source']
146*9c5db199SXin Li        if not os.path.isabs(target):
147*9c5db199SXin Li            raise SSPDeployError('Target path must be absolute path: %s' %
148*9c5db199SXin Li                                 target)
149*9c5db199SXin Li        if not os.path.isabs(source):
150*9c5db199SXin Li            if source.startswith('~'):
151*9c5db199SXin Li                # This is to handle the case that the script is run with sudo.
152*9c5db199SXin Li                inject_user_path = ('~%s%s' % (utils.get_real_user(),
153*9c5db199SXin Li                                               source[1:]))
154*9c5db199SXin Li                source = os.path.expanduser(inject_user_path)
155*9c5db199SXin Li            else:
156*9c5db199SXin Li                source = os.path.join(common.autotest_dir, source)
157*9c5db199SXin Li            # Update the source setting in deploy config with the updated path.
158*9c5db199SXin Li            deploy_config['source'] = source
159*9c5db199SXin Li
160*9c5db199SXin Li
161*9c5db199SXin Li    @staticmethod
162*9c5db199SXin Li    def validate(deploy_config):
163*9c5db199SXin Li        """Validate the deploy config.
164*9c5db199SXin Li
165*9c5db199SXin Li        Deploy configs need to be validated and pre-processed, e.g.,
166*9c5db199SXin Li        1. Target must be an absolute path.
167*9c5db199SXin Li        2. Source must be updated to be an absolute path.
168*9c5db199SXin Li
169*9c5db199SXin Li        @param deploy_config: A dictionary of deploy config to be validated.
170*9c5db199SXin Li
171*9c5db199SXin Li        @return: A DeployConfig object that contains the deploy config.
172*9c5db199SXin Li
173*9c5db199SXin Li        @raise SSPDeployError: If the deploy config is invalid.
174*9c5db199SXin Li
175*9c5db199SXin Li        """
176*9c5db199SXin Li        DeployConfigManager.validate_path(deploy_config)
177*9c5db199SXin Li        return DeployConfig(**deploy_config)
178*9c5db199SXin Li
179*9c5db199SXin Li
180*9c5db199SXin Li    @staticmethod
181*9c5db199SXin Li    def validate_mount(deploy_config):
182*9c5db199SXin Li        """Validate the deploy config for mounting a directory.
183*9c5db199SXin Li
184*9c5db199SXin Li        Deploy configs need to be validated and pre-processed, e.g.,
185*9c5db199SXin Li        1. Target must be an absolute path.
186*9c5db199SXin Li        2. Source must be updated to be an absolute path.
187*9c5db199SXin Li        3. Mount must be true.
188*9c5db199SXin Li
189*9c5db199SXin Li        @param deploy_config: A dictionary of deploy config to be validated.
190*9c5db199SXin Li
191*9c5db199SXin Li        @return: A DeployConfig object that contains the deploy config.
192*9c5db199SXin Li
193*9c5db199SXin Li        @raise SSPDeployError: If the deploy config is invalid.
194*9c5db199SXin Li
195*9c5db199SXin Li        """
196*9c5db199SXin Li        DeployConfigManager.validate_path(deploy_config)
197*9c5db199SXin Li        c = MountConfig(**deploy_config)
198*9c5db199SXin Li        if not c.mount:
199*9c5db199SXin Li            raise SSPDeployError('`mount` must be true.')
200*9c5db199SXin Li        if not c.force_create and not os.path.exists(c.source):
201*9c5db199SXin Li            raise SSPDeployError('`source` does not exist.')
202*9c5db199SXin Li        return c
203*9c5db199SXin Li
204*9c5db199SXin Li
205*9c5db199SXin Li    def __init__(self, container, config_file=None):
206*9c5db199SXin Li        """Initialize the deploy config manager.
207*9c5db199SXin Li
208*9c5db199SXin Li        @param container: The container needs to deploy config.
209*9c5db199SXin Li        @param config_file: An optional config file.  For testing.
210*9c5db199SXin Li        """
211*9c5db199SXin Li        self.container = container
212*9c5db199SXin Li        # If shadow config is used, the deployment procedure will skip some
213*9c5db199SXin Li        # special handling of config file, e.g.,
214*9c5db199SXin Li        # 1. Set enable_main_ssh to False in autotest shadow config.
215*9c5db199SXin Li        # 2. Set ssh logleve to ERROR for all hosts.
216*9c5db199SXin Li        if config_file is None:
217*9c5db199SXin Li            self.is_shadow_config = os.path.exists(
218*9c5db199SXin Li                    SSP_DEPLOY_SHADOW_CONFIG_FILE)
219*9c5db199SXin Li            config_file = (
220*9c5db199SXin Li                    SSP_DEPLOY_SHADOW_CONFIG_FILE if self.is_shadow_config
221*9c5db199SXin Li                    else SSP_DEPLOY_CONFIG_FILE)
222*9c5db199SXin Li        else:
223*9c5db199SXin Li            self.is_shadow_config = False
224*9c5db199SXin Li
225*9c5db199SXin Li        with open(config_file) as f:
226*9c5db199SXin Li            deploy_configs = json.load(f)
227*9c5db199SXin Li        self.deploy_configs = [self.validate(c) for c in deploy_configs
228*9c5db199SXin Li                               if 'append' in c]
229*9c5db199SXin Li        self.mount_configs = [self.validate_mount(c) for c in deploy_configs
230*9c5db199SXin Li                              if 'mount' in c]
231*9c5db199SXin Li        tmp_append = os.path.join(self.container.rootfs,
232*9c5db199SXin Li                                  _APPEND_FOLDER.lstrip(os.path.sep))
233*9c5db199SXin Li        commands = []
234*9c5db199SXin Li        if lxc_utils.path_exists(tmp_append):
235*9c5db199SXin Li            commands = ['rm -rf "%s"' % tmp_append]
236*9c5db199SXin Li        commands.append('mkdir -p "%s"' % tmp_append)
237*9c5db199SXin Li        lxc_utils.sudo_commands(commands)
238*9c5db199SXin Li
239*9c5db199SXin Li
240*9c5db199SXin Li    def _deploy_config_pre_start(self, deploy_config):
241*9c5db199SXin Li        """Deploy a config before container is started.
242*9c5db199SXin Li
243*9c5db199SXin Li        Most configs can be deployed before the container is up. For configs
244*9c5db199SXin Li        require a reboot to take effective, they must be deployed in this
245*9c5db199SXin Li        function.
246*9c5db199SXin Li
247*9c5db199SXin Li        @param deploy_config: Config to be deployed.
248*9c5db199SXin Li        """
249*9c5db199SXin Li        if not lxc_utils.path_exists(deploy_config.source):
250*9c5db199SXin Li            return
251*9c5db199SXin Li        # Path to the target file relative to host.
252*9c5db199SXin Li        if deploy_config.append:
253*9c5db199SXin Li            target = os.path.join(_APPEND_FOLDER,
254*9c5db199SXin Li                                  os.path.basename(deploy_config.target))
255*9c5db199SXin Li        else:
256*9c5db199SXin Li            target = deploy_config.target
257*9c5db199SXin Li
258*9c5db199SXin Li        self.container.copy(deploy_config.source, target)
259*9c5db199SXin Li
260*9c5db199SXin Li
261*9c5db199SXin Li    def _deploy_config_post_start(self, deploy_config):
262*9c5db199SXin Li        """Deploy a config after container is started.
263*9c5db199SXin Li
264*9c5db199SXin Li        For configs to be appended after the existing config files in container,
265*9c5db199SXin Li        they must be copied to a temp location before container is up (deployed
266*9c5db199SXin Li        in function _deploy_config_pre_start). After the container is up, calls
267*9c5db199SXin Li        can be made to append the content of such configs to existing config
268*9c5db199SXin Li        files.
269*9c5db199SXin Li
270*9c5db199SXin Li        @param deploy_config: Config to be deployed.
271*9c5db199SXin Li
272*9c5db199SXin Li        """
273*9c5db199SXin Li        if deploy_config.append:
274*9c5db199SXin Li            source = os.path.join(_APPEND_FOLDER,
275*9c5db199SXin Li                                  os.path.basename(deploy_config.target))
276*9c5db199SXin Li            self.container.attach_run('cat \'%s\' >> \'%s\'' %
277*9c5db199SXin Li                                      (source, deploy_config.target))
278*9c5db199SXin Li        self.container.attach_run(
279*9c5db199SXin Li                'chmod -R %s \'%s\'' %
280*9c5db199SXin Li                (deploy_config.permission, deploy_config.target))
281*9c5db199SXin Li
282*9c5db199SXin Li
283*9c5db199SXin Li    def _modify_shadow_config(self):
284*9c5db199SXin Li        """Update the shadow config used in container with correct values.
285*9c5db199SXin Li
286*9c5db199SXin Li        This only applies when no shadow SSP deploy config is applied. For
287*9c5db199SXin Li        default SSP deploy config, autotest shadow_config.ini is from autotest
288*9c5db199SXin Li        directory, which requires following modification to be able to work in
289*9c5db199SXin Li        container. If one chooses to use a shadow SSP deploy config file, the
290*9c5db199SXin Li        autotest shadow_config.ini must be from a source with following
291*9c5db199SXin Li        modification:
292*9c5db199SXin Li        1. Disable main ssh connection in shadow config, as it is not working
293*9c5db199SXin Li           properly in container yet, and produces noise in the log.
294*9c5db199SXin Li        2. Update AUTOTEST_WEB/host and SERVER/hostname to be the IP of the host
295*9c5db199SXin Li           if any is set to localhost or 127.0.0.1. Otherwise, set it to be the
296*9c5db199SXin Li           FQDN of the config value.
297*9c5db199SXin Li        3. Update SSP/user, which is used as the user makes RPC inside the
298*9c5db199SXin Li           container. This allows the RPC to pass ACL check as if the call is
299*9c5db199SXin Li           made in the host.
300*9c5db199SXin Li
301*9c5db199SXin Li        """
302*9c5db199SXin Li        shadow_config = os.path.join(constants.CONTAINER_AUTOTEST_DIR,
303*9c5db199SXin Li                                     'shadow_config.ini')
304*9c5db199SXin Li
305*9c5db199SXin Li        # Inject "AUTOSERV/enable_main_ssh: False" in shadow config as
306*9c5db199SXin Li        # container does not support main ssh connection yet.
307*9c5db199SXin Li        self.container.attach_run(
308*9c5db199SXin Li                'echo $\'\n[AUTOSERV]\nenable_main_ssh: False\n\' >> %s' %
309*9c5db199SXin Li                shadow_config)
310*9c5db199SXin Li
311*9c5db199SXin Li        host_ip = lxc_utils.get_host_ip()
312*9c5db199SXin Li        local_names = ['localhost', '127.0.0.1']
313*9c5db199SXin Li
314*9c5db199SXin Li        db_host = config.get_config_value('AUTOTEST_WEB', 'host')
315*9c5db199SXin Li        if db_host.lower() in local_names:
316*9c5db199SXin Li            new_host = host_ip
317*9c5db199SXin Li        else:
318*9c5db199SXin Li            new_host = socket.getfqdn(db_host)
319*9c5db199SXin Li        self.container.attach_run('echo $\'\n[AUTOTEST_WEB]\nhost: %s\n\' >> %s'
320*9c5db199SXin Li                                  % (new_host, shadow_config))
321*9c5db199SXin Li
322*9c5db199SXin Li        afe_host = config.get_config_value('SERVER', 'hostname')
323*9c5db199SXin Li        if afe_host.lower() in local_names:
324*9c5db199SXin Li            new_host = host_ip
325*9c5db199SXin Li        else:
326*9c5db199SXin Li            new_host = socket.getfqdn(afe_host)
327*9c5db199SXin Li        self.container.attach_run('echo $\'\n[SERVER]\nhostname: %s\n\' >> %s' %
328*9c5db199SXin Li                                  (new_host, shadow_config))
329*9c5db199SXin Li
330*9c5db199SXin Li        # Update configurations in SSP section:
331*9c5db199SXin Li        # user: The user running current process.
332*9c5db199SXin Li        # is_moblab: True if the autotest server is a Moblab instance.
333*9c5db199SXin Li        # host_container_ip: IP address of the lxcbr0 interface. Process running
334*9c5db199SXin Li        #     inside container can make RPC through this IP.
335*9c5db199SXin Li        self.container.attach_run(
336*9c5db199SXin Li                'echo $\'\n[SSP]\nuser: %s\nis_moblab: %s\n'
337*9c5db199SXin Li                'host_container_ip: %s\n\' >> %s' %
338*9c5db199SXin Li                (getpass.getuser(), bool(utils.is_moblab()),
339*9c5db199SXin Li                 lxc_utils.get_host_ip(), shadow_config))
340*9c5db199SXin Li
341*9c5db199SXin Li
342*9c5db199SXin Li    def _modify_ssh_config(self):
343*9c5db199SXin Li        """Modify ssh config for it to work inside container.
344*9c5db199SXin Li
345*9c5db199SXin Li        This is only called when default ssp_deploy_config is used. If shadow
346*9c5db199SXin Li        deploy config is manually set up, this function will not be called.
347*9c5db199SXin Li        Therefore, the source of ssh config must be properly updated to be able
348*9c5db199SXin Li        to work inside container.
349*9c5db199SXin Li
350*9c5db199SXin Li        """
351*9c5db199SXin Li        # Remove domain specific flags.
352*9c5db199SXin Li        ssh_config = '/root/.ssh/config'
353*9c5db199SXin Li        self.container.attach_run('sed -i \'s/UseProxyIf=false//g\' \'%s\'' %
354*9c5db199SXin Li                                  ssh_config)
355*9c5db199SXin Li        # TODO(dshi): crbug.com/451622 ssh connection loglevel is set to
356*9c5db199SXin Li        # ERROR in container before the ssh connection works. This is
357*9c5db199SXin Li        # to avoid logs being flooded with warning `Permanently added
358*9c5db199SXin Li        # '[hostname]' (RSA) to the list of known hosts.` (crbug.com/478364)
359*9c5db199SXin Li        # The sed command injects following at the beginning of .ssh/config
360*9c5db199SXin Li        # used in config. With such change, ssh command will not post
361*9c5db199SXin Li        # warnings.
362*9c5db199SXin Li        # Host *
363*9c5db199SXin Li        #   LogLevel Error
364*9c5db199SXin Li        self.container.attach_run(
365*9c5db199SXin Li                'sed -i \'1s/^/Host *\\n  LogLevel ERROR\\n\\n/\' \'%s\'' %
366*9c5db199SXin Li                ssh_config)
367*9c5db199SXin Li
368*9c5db199SXin Li        # Inject ssh config for moblab to ssh to dut from container.
369*9c5db199SXin Li        if utils.is_moblab():
370*9c5db199SXin Li            # ssh to moblab itself using moblab user.
371*9c5db199SXin Li            self.container.attach_run(
372*9c5db199SXin Li                    'echo $\'\nHost 192.168.231.1\n  User moblab\n  '
373*9c5db199SXin Li                    'IdentityFile %%d/.ssh/testing_rsa\' >> %s' %
374*9c5db199SXin Li                    '/root/.ssh/config')
375*9c5db199SXin Li            # ssh to duts using root user.
376*9c5db199SXin Li            self.container.attach_run(
377*9c5db199SXin Li                    'echo $\'\nHost *\n  User root\n  '
378*9c5db199SXin Li                    'IdentityFile %%d/.ssh/testing_rsa\' >> %s' %
379*9c5db199SXin Li                    '/root/.ssh/config')
380*9c5db199SXin Li
381*9c5db199SXin Li
382*9c5db199SXin Li    def deploy_pre_start(self):
383*9c5db199SXin Li        """Deploy configs before the container is started.
384*9c5db199SXin Li        """
385*9c5db199SXin Li        for deploy_config in self.deploy_configs:
386*9c5db199SXin Li            self._deploy_config_pre_start(deploy_config)
387*9c5db199SXin Li        for mount_config in self.mount_configs:
388*9c5db199SXin Li            if (mount_config.force_create and
389*9c5db199SXin Li                not os.path.exists(mount_config.source)):
390*9c5db199SXin Li                utils.run('mkdir -p %s' % mount_config.source)
391*9c5db199SXin Li            self.container.mount_dir(mount_config.source,
392*9c5db199SXin Li                                     mount_config.target,
393*9c5db199SXin Li                                     mount_config.readonly)
394*9c5db199SXin Li
395*9c5db199SXin Li
396*9c5db199SXin Li    def deploy_post_start(self):
397*9c5db199SXin Li        """Deploy configs after the container is started.
398*9c5db199SXin Li        """
399*9c5db199SXin Li        for deploy_config in self.deploy_configs:
400*9c5db199SXin Li            self._deploy_config_post_start(deploy_config)
401*9c5db199SXin Li        # Autotest shadow config requires special handling to update hostname
402*9c5db199SXin Li        # of `localhost` with host IP. Shards always use `localhost` as value
403*9c5db199SXin Li        # of SERVER\hostname and AUTOTEST_WEB\host.
404*9c5db199SXin Li        self._modify_shadow_config()
405*9c5db199SXin Li        # Only apply special treatment for files deployed by the default
406*9c5db199SXin Li        # ssp_deploy_config
407*9c5db199SXin Li        if not self.is_shadow_config:
408*9c5db199SXin Li            self._modify_ssh_config()
409