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