1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.config; 17 18 import com.android.tradefed.log.LogUtil.CLog; 19 import com.android.tradefed.sandbox.ISandbox; 20 import com.android.tradefed.sandbox.SandboxConfigDump; 21 import com.android.tradefed.sandbox.SandboxConfigDump.DumpCmd; 22 import com.android.tradefed.sandbox.SandboxConfigUtil; 23 import com.android.tradefed.sandbox.SandboxConfigurationException; 24 import com.android.tradefed.util.FileUtil; 25 import com.android.tradefed.util.IRunUtil; 26 import com.android.tradefed.util.keystore.IKeyStoreClient; 27 import com.android.tradefed.util.keystore.IKeyStoreFactory; 28 import com.android.tradefed.util.keystore.KeyStoreException; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Map.Entry; 39 import java.util.Set; 40 import java.util.stream.Collectors; 41 42 /** 43 * Special Configuration factory to handle creation of configurations for Sandboxing purpose. 44 * 45 * <p>TODO: Split the configuration dump part to another class 46 */ 47 public class SandboxConfigurationFactory extends ConfigurationFactory { 48 49 private static SandboxConfigurationFactory sInstance = null; 50 public static final Set<String> OPTION_IGNORED_ELEMENTS = new HashSet<>(); 51 52 static { 53 OPTION_IGNORED_ELEMENTS.addAll(SandboxConfigDump.VERSIONED_ELEMENTS); 54 OPTION_IGNORED_ELEMENTS.add(Configuration.DEVICE_REQUIREMENTS_TYPE_NAME); 55 OPTION_IGNORED_ELEMENTS.add(Configuration.DEVICE_OPTIONS_TYPE_NAME); 56 OPTION_IGNORED_ELEMENTS.add(Configuration.DEVICE_RECOVERY_TYPE_NAME); 57 OPTION_IGNORED_ELEMENTS.add(Configuration.CMD_OPTIONS_TYPE_NAME); 58 } 59 60 /** Get the singleton {@link IConfigurationFactory} instance. */ getInstance()61 public static SandboxConfigurationFactory getInstance() { 62 if (sInstance == null) { 63 sInstance = new SandboxConfigurationFactory(); 64 } 65 return sInstance; 66 } 67 68 /** {@inheritDoc} */ 69 @Override getConfigurationDef( String name, boolean isGlobal, Map<String, String> templateMap)70 protected ConfigurationDef getConfigurationDef( 71 String name, boolean isGlobal, Map<String, String> templateMap) 72 throws ConfigurationException { 73 // TODO: Extend ConfigurationDef to possibly create a different IConfiguration type and 74 // handle more elegantly the parent/subprocess incompatibilities. 75 ConfigurationDef def = createConfigurationDef(name); 76 new ConfigLoader(isGlobal).loadConfiguration(name, def, null, templateMap, null); 77 return def; 78 } 79 80 /** Internal method to create {@link ConfigurationDef} */ createConfigurationDef(String name)81 protected ConfigurationDef createConfigurationDef(String name) { 82 return new ConfigurationDef(name); 83 } 84 85 /** 86 * When running the dump for a command. Create a config with specific expectations. 87 * 88 * @param arrayArgs the command line for the run. 89 * @param command The dump command in progress 90 * @return a {@link IConfiguration} valid for the VERSIONED Sandbox. 91 * @throws ConfigurationException 92 */ createConfigurationFromArgs(String[] arrayArgs, DumpCmd command)93 public IConfiguration createConfigurationFromArgs(String[] arrayArgs, DumpCmd command) 94 throws ConfigurationException { 95 // Create on a new object to avoid state on the factory. 96 SandboxConfigurationFactory loader = new RunSandboxConfigurationFactory(command); 97 return loader.createConfigurationFromArgs(arrayArgs, null, getKeyStoreClient()); 98 } 99 100 /** 101 * Create a {@link IConfiguration} based on the command line and sandbox provided. 102 * 103 * @param args the command line for the run. 104 * @param keyStoreClient the {@link IKeyStoreClient} where to load the key from. 105 * @param sandbox the {@link ISandbox} used for the run. 106 * @param runUtil the {@link IRunUtil} to run commands. 107 * @return a {@link IConfiguration} valid for the sandbox. 108 * @throws ConfigurationException 109 */ createConfigurationFromArgs( String[] args, IKeyStoreClient keyStoreClient, ISandbox sandbox, IRunUtil runUtil)110 public IConfiguration createConfigurationFromArgs( 111 String[] args, IKeyStoreClient keyStoreClient, ISandbox sandbox, IRunUtil runUtil) 112 throws ConfigurationException { 113 IConfiguration config = null; 114 File xmlConfig = null; 115 File globalConfig = null; 116 try { 117 runUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE); 118 // Dump the NON_VERSIONED part of the configuration against the current TF and not the 119 // sandboxed environment. 120 globalConfig = SandboxConfigUtil.dumpFilteredGlobalConfig(new HashSet<>()); 121 xmlConfig = 122 SandboxConfigUtil.dumpConfigForVersion( 123 createClasspath(), 124 runUtil, 125 args, 126 DumpCmd.NON_VERSIONED_CONFIG, 127 globalConfig); 128 // Get the non version part of the configuration in order to do proper allocation 129 // of devices and such. 130 config = 131 super.createConfigurationFromArgs( 132 new String[] {xmlConfig.getAbsolutePath()}, null, keyStoreClient); 133 // Reset the command line to the original one. 134 config.setCommandLine(args); 135 config.setConfigurationObject(Configuration.SANDBOX_TYPE_NAME, sandbox); 136 } catch (SandboxConfigurationException e) { 137 // Handle the thin launcher mode: Configuration does not exists in parent version yet. 138 config = sandbox.createThinLauncherConfig(args, keyStoreClient, runUtil, globalConfig); 139 if (config == null) { 140 // Rethrow the original exception. 141 CLog.e(e); 142 throw e; 143 } 144 } catch (IOException e) { 145 CLog.e(e); 146 throw new ConfigurationException("Failed to dump global config.", e); 147 } catch (ConfigurationException e) { 148 CLog.e(e); 149 throw e; 150 } finally { 151 if (config == null) { 152 // In case of error, tear down the sandbox. 153 sandbox.tearDown(); 154 } 155 FileUtil.deleteFile(globalConfig); 156 FileUtil.deleteFile(xmlConfig); 157 } 158 return config; 159 } 160 getKeyStoreClient()161 private IKeyStoreClient getKeyStoreClient() { 162 try { 163 IKeyStoreFactory f = GlobalConfiguration.getInstance().getKeyStoreFactory(); 164 if (f != null) { 165 try { 166 return f.createKeyStoreClient(); 167 } catch (KeyStoreException e) { 168 CLog.e("Failed to create key store client"); 169 CLog.e(e); 170 } 171 } 172 } catch (IllegalStateException e) { 173 CLog.w("Global configuration has not been created, failed to get keystore"); 174 CLog.e(e); 175 } 176 return null; 177 } 178 179 /** Returns the classpath of the current running Tradefed. */ createClasspath()180 private String createClasspath() throws ConfigurationException { 181 // Get the classpath property. 182 String classpathStr = System.getProperty("java.class.path"); 183 if (classpathStr == null) { 184 throw new ConfigurationException( 185 "Could not find the classpath property: java.class.path"); 186 } 187 return classpathStr; 188 } 189 190 private class RunSandboxConfigurationFactory extends SandboxConfigurationFactory { 191 192 private DumpCmd mCommand; 193 RunSandboxConfigurationFactory(DumpCmd command)194 RunSandboxConfigurationFactory(DumpCmd command) { 195 mCommand = command; 196 } 197 198 @Override createConfigurationDef(String name)199 protected ConfigurationDef createConfigurationDef(String name) { 200 return new ConfigurationDef(name) { 201 @Override 202 protected void checkRejectedObjects( 203 Map<String, String> rejectedObjects, Throwable cause) 204 throws ClassNotFoundConfigurationException { 205 if (mCommand.equals(DumpCmd.RUN_CONFIG) || mCommand.equals(DumpCmd.TEST_MODE)) { 206 Map<String, String> copyRejected = new HashMap<>(); 207 for (Entry<String, String> item : rejectedObjects.entrySet()) { 208 if (SandboxConfigDump.VERSIONED_ELEMENTS.contains(item.getValue())) { 209 copyRejected.put(item.getKey(), item.getValue()); 210 } 211 } 212 super.checkRejectedObjects(copyRejected, cause); 213 } else { 214 super.checkRejectedObjects(rejectedObjects, cause); 215 } 216 } 217 218 @Override 219 protected void injectOptions(IConfiguration config, List<OptionDef> optionList) 220 throws ConfigurationException { 221 List<OptionDef> individualAttempt = new ArrayList<>(); 222 if (mCommand.equals(DumpCmd.RUN_CONFIG) || mCommand.equals(DumpCmd.TEST_MODE)) { 223 individualAttempt = 224 optionList 225 .stream() 226 .filter( 227 o -> 228 (o.applicableObjectType != null 229 && !OPTION_IGNORED_ELEMENTS 230 .contains( 231 o.applicableObjectType))) 232 .collect(Collectors.toList()); 233 optionList.removeAll(individualAttempt); 234 } 235 super.injectOptions(config, optionList); 236 // Try individually each "filtered-option", they will not stop the run if they 237 // cannot be set since they are not part of the versioned process. 238 for (OptionDef item : individualAttempt) { 239 List<OptionDef> tmpList = Arrays.asList(item); 240 try { 241 super.injectOptions(config, tmpList); 242 } catch (ConfigurationException e) { 243 // Ignore 244 } 245 } 246 } 247 }; 248 } 249 } 250 } 251