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