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.sandbox; 17 18 import com.android.ddmlib.Log.LogLevel; 19 import com.android.tradefed.build.StubBuildProvider; 20 import com.android.tradefed.config.Configuration; 21 import com.android.tradefed.config.ConfigurationException; 22 import com.android.tradefed.config.GlobalConfiguration; 23 import com.android.tradefed.config.IConfiguration; 24 import com.android.tradefed.config.IDeviceConfiguration; 25 import com.android.tradefed.config.SandboxConfigurationFactory; 26 import com.android.tradefed.device.IDeviceSelection; 27 import com.android.tradefed.log.FileLogger; 28 import com.android.tradefed.log.ILeveledLogOutput; 29 import com.android.tradefed.result.FileSystemLogSaver; 30 import com.android.tradefed.result.ILogSaver; 31 import com.android.tradefed.result.ITestInvocationListener; 32 import com.android.tradefed.result.SubprocessResultsReporter; 33 import com.android.tradefed.result.proto.StreamProtoResultReporter; 34 import com.android.tradefed.testtype.SubprocessTfLauncher; 35 import com.android.tradefed.util.StreamUtil; 36 import com.android.tradefed.util.keystore.IKeyStoreClient; 37 import com.android.tradefed.util.keystore.KeyStoreException; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.io.PrintWriter; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Set; 47 import java.util.regex.Matcher; 48 import java.util.regex.Pattern; 49 50 /** 51 * Runner class that creates a {@link IConfiguration} based on a command line and dump it to a file. 52 * args: <DumpCmd> <output File> <remaing command line> 53 */ 54 public class SandboxConfigDump { 55 56 public enum DumpCmd { 57 /** The full xml based on the command line will be outputted */ 58 FULL_XML, 59 /** Only non-versioned element of the xml will be outputted */ 60 NON_VERSIONED_CONFIG, 61 /** A run-ready config will be outputted */ 62 RUN_CONFIG, 63 /** Special mode that allows the sandbox to generate another layer of sandboxing. */ 64 TEST_MODE, 65 /** Mode used to dump the test template only. */ 66 STRICT_TEST 67 } 68 69 /** 70 * We do not output the versioned elements to avoid causing the parent process to have issues 71 * with them when trying to resolve them 72 */ 73 public static final Set<String> VERSIONED_ELEMENTS = new HashSet<>(); 74 75 static { 76 VERSIONED_ELEMENTS.add(Configuration.SYSTEM_STATUS_CHECKER_TYPE_NAME); 77 VERSIONED_ELEMENTS.add(Configuration.DEVICE_METRICS_COLLECTOR_TYPE_NAME); 78 VERSIONED_ELEMENTS.add(Configuration.METRIC_POST_PROCESSOR_TYPE_NAME); 79 VERSIONED_ELEMENTS.add(Configuration.MULTI_PRE_TARGET_PREPARER_TYPE_NAME); 80 VERSIONED_ELEMENTS.add(Configuration.MULTI_PREPARER_TYPE_NAME); 81 VERSIONED_ELEMENTS.add(Configuration.TARGET_PREPARER_TYPE_NAME); 82 VERSIONED_ELEMENTS.add(Configuration.TEST_TYPE_NAME); 83 } 84 85 /** Elements that are not related to tests of the config but to the surrounding invocation. */ 86 private static final Set<String> NON_TEST_ELEMENTS = new HashSet<>(); 87 88 static { 89 NON_TEST_ELEMENTS.add(Configuration.BUILD_PROVIDER_TYPE_NAME); 90 NON_TEST_ELEMENTS.add(Configuration.LOGGER_TYPE_NAME); 91 NON_TEST_ELEMENTS.add(Configuration.DEVICE_RECOVERY_TYPE_NAME); 92 NON_TEST_ELEMENTS.add(Configuration.LOG_SAVER_TYPE_NAME); 93 NON_TEST_ELEMENTS.add(Configuration.RESULT_REPORTER_TYPE_NAME); 94 NON_TEST_ELEMENTS.add(Configuration.DEVICE_REQUIREMENTS_TYPE_NAME); 95 NON_TEST_ELEMENTS.add(Configuration.DEVICE_OPTIONS_TYPE_NAME); 96 NON_TEST_ELEMENTS.add(Configuration.COVERAGE_OPTIONS_TYPE_NAME); 97 NON_TEST_ELEMENTS.add(Configuration.RETRY_DECISION_TYPE_NAME); 98 NON_TEST_ELEMENTS.add(Configuration.SANBOX_OPTIONS_TYPE_NAME); 99 NON_TEST_ELEMENTS.add(Configuration.CMD_OPTIONS_TYPE_NAME); 100 NON_TEST_ELEMENTS.add(Configuration.CONFIGURATION_DESCRIPTION_TYPE_NAME); 101 NON_TEST_ELEMENTS.add(Configuration.GLOBAL_FILTERS_TYPE_NAME); 102 NON_TEST_ELEMENTS.add(Configuration.SKIP_MANAGER_TYPE_NAME); 103 } 104 105 /** 106 * Parse the args and creates a {@link IConfiguration} from it then dumps it to the result file. 107 */ parse(String[] args)108 public int parse(String[] args) { 109 // TODO: add some more checking 110 List<String> argList = new ArrayList<>(Arrays.asList(args)); 111 DumpCmd cmd = DumpCmd.valueOf(argList.remove(0)); 112 File resFile = new File(argList.remove(0)); 113 SandboxConfigurationFactory factory = SandboxConfigurationFactory.getInstance(); 114 PrintWriter pw = null; 115 try { 116 if (DumpCmd.RUN_CONFIG.equals(cmd) 117 && GlobalConfiguration.getInstance().getKeyStoreFactory() != null) { 118 IKeyStoreClient keyClient = 119 GlobalConfiguration.getInstance() 120 .getKeyStoreFactory() 121 .createKeyStoreClient(); 122 replaceKeystore(keyClient, argList); 123 } 124 IConfiguration config = 125 factory.createConfigurationFromArgs(argList.toArray(new String[0]), cmd); 126 if (DumpCmd.RUN_CONFIG.equals(cmd) || DumpCmd.TEST_MODE.equals(cmd)) { 127 config.getCommandOptions().setShouldUseSandboxing(false); 128 config.getConfigurationDescription().setSandboxed(true); 129 // Don't use replication in the sandbox 130 config.getCommandOptions().setReplicateSetup(false); 131 // Override build providers since they occur in parents 132 for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) { 133 deviceConfig.addSpecificConfig( 134 new StubBuildProvider(), Configuration.BUILD_PROVIDER_TYPE_NAME); 135 } 136 // Set the reporter 137 ITestInvocationListener reporter = null; 138 if (getSandboxOptions(config).shouldUseProtoReporter()) { 139 reporter = new StreamProtoResultReporter(); 140 } else { 141 reporter = new SubprocessResultsReporter(); 142 ((SubprocessResultsReporter) reporter).setOutputTestLog(true); 143 } 144 config.setTestInvocationListener(reporter); 145 // Set log level for sandbox 146 ILeveledLogOutput logger = config.getLogOutput(); 147 logger.setLogLevel(LogLevel.VERBOSE); 148 if (logger instanceof FileLogger) { 149 // Ensure we get the stdout logging in FileLogger case. 150 ((FileLogger) logger).setLogLevelDisplay(LogLevel.VERBOSE); 151 } 152 153 ILogSaver logSaver = config.getLogSaver(); 154 if (logSaver instanceof FileSystemLogSaver) { 155 // Send the files directly, the parent will take care of compression if needed 156 ((FileSystemLogSaver) logSaver).setCompressFiles(false); 157 } 158 159 // Ensure in special conditions (placeholder devices) we can still allocate. 160 secureDeviceAllocation(config); 161 162 // Mark as subprocess 163 config.getCommandOptions() 164 .getInvocationData() 165 .put(SubprocessTfLauncher.SUBPROCESS_TAG_NAME, "true"); 166 } 167 if (DumpCmd.TEST_MODE.equals(cmd)) { 168 // We allow one more layer of sandbox to be generated 169 config.getCommandOptions().setShouldUseSandboxing(true); 170 config.getConfigurationDescription().setSandboxed(false); 171 // Ensure we turn off test mode afterward to avoid infinite sandboxing 172 config.getCommandOptions().setUseSandboxTestMode(false); 173 } 174 pw = new PrintWriter(resFile); 175 if (DumpCmd.NON_VERSIONED_CONFIG.equals(cmd)) { 176 // Remove elements that are versioned. 177 config.dumpXml( 178 pw, 179 new ArrayList<>(VERSIONED_ELEMENTS), 180 true, 181 /* Don't print unchanged options */ false); 182 } else if (DumpCmd.STRICT_TEST.equals(cmd)) { 183 config.dumpXml( 184 pw, 185 new ArrayList<>(NON_TEST_ELEMENTS), 186 true, 187 /* Don't print unchanged options */ false); 188 } else { 189 // FULL_XML in that case. 190 config.dumpXml(pw, new ArrayList<>(), true, 191 /* Don't print unchanged options */ false); 192 } 193 } catch (ConfigurationException | IOException | KeyStoreException e) { 194 e.printStackTrace(); 195 return 1; 196 } finally { 197 StreamUtil.close(pw); 198 } 199 return 0; 200 } 201 main(final String[] mainArgs)202 public static void main(final String[] mainArgs) { 203 try { 204 GlobalConfiguration.createGlobalConfiguration(new String[] {}); 205 } catch (ConfigurationException e) { 206 e.printStackTrace(); 207 System.exit(1); 208 } 209 SandboxConfigDump configDump = new SandboxConfigDump(); 210 int code = configDump.parse(mainArgs); 211 System.exit(code); 212 } 213 214 /** Replace keystore options in place. */ replaceKeystore(IKeyStoreClient keyClient, List<String> argList)215 public static void replaceKeystore(IKeyStoreClient keyClient, List<String> argList) { 216 Pattern USE_KEYSTORE_REGEX = Pattern.compile("(.*)USE_KEYSTORE@([^:]*)(.*)"); 217 for (int i = 0; i < argList.size(); i++) { 218 Matcher m = USE_KEYSTORE_REGEX.matcher(argList.get(i)); 219 if (m.matches() && m.groupCount() > 0) { 220 String key = m.group(2); 221 String keyValue = keyClient.fetchKey(key); 222 String newValue = argList.get(i).replaceAll("USE_KEYSTORE@" + key, keyValue); 223 argList.set(i, newValue); 224 } 225 } 226 } 227 getSandboxOptions(IConfiguration config)228 private SandboxOptions getSandboxOptions(IConfiguration config) { 229 return (SandboxOptions) 230 config.getConfigurationObject(Configuration.SANBOX_OPTIONS_TYPE_NAME); 231 } 232 secureDeviceAllocation(IConfiguration config)233 private void secureDeviceAllocation(IConfiguration config) { 234 for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) { 235 IDeviceSelection requirements = deviceConfig.getDeviceRequirements(); 236 if (requirements.nullDeviceRequested() 237 || requirements.gceDeviceRequested()) { 238 // Reset serials, ensure any null/gce-device can be selected. 239 requirements.setSerial(); 240 } 241 // Reset device requested type, we don't need it in the sandbox 242 requirements.setBaseDeviceTypeRequested(null); 243 // In sandbox it's pointless to check again for battery for allocation 244 requirements.setRequireBatteryCheck(false); 245 } 246 } 247 } 248