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.config.Configuration;
20 import com.android.tradefed.config.ConfigurationException;
21 import com.android.tradefed.config.GlobalConfiguration;
22 import com.android.tradefed.config.IConfiguration;
23 import com.android.tradefed.config.IDeviceConfiguration;
24 import com.android.tradefed.config.SandboxConfigurationFactory;
25 import com.android.tradefed.device.IDeviceSelection;
26 import com.android.tradefed.log.FileLogger;
27 import com.android.tradefed.log.ILeveledLogOutput;
28 import com.android.tradefed.result.FileSystemLogSaver;
29 import com.android.tradefed.result.ILogSaver;
30 import com.android.tradefed.result.ITestInvocationListener;
31 import com.android.tradefed.result.SubprocessResultsReporter;
32 import com.android.tradefed.result.proto.StreamProtoResultReporter;
33 import com.android.tradefed.testtype.SubprocessTfLauncher;
34 import com.android.tradefed.util.StreamUtil;
35 
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.PrintWriter;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Set;
44 
45 /**
46  * Runner class that creates a {@link IConfiguration} based on a command line and dump it to a file.
47  * args: <DumpCmd> <output File> <remaing command line>
48  */
49 public class SandboxConfigDump {
50 
51     public enum DumpCmd {
52         /** The full xml based on the command line will be outputted */
53         FULL_XML,
54         /** Only non-versioned element of the xml will be outputted */
55         NON_VERSIONED_CONFIG,
56         /** A run-ready config will be outputted */
57         RUN_CONFIG,
58         /** Special mode that allows the sandbox to generate another layer of sandboxing. */
59         TEST_MODE,
60     }
61 
62     /**
63      * We do not output the versioned elements to avoid causing the parent process to have issues
64      * with them when trying to resolve them
65      */
66     public static final Set<String> VERSIONED_ELEMENTS = new HashSet<>();
67 
68     static {
69         VERSIONED_ELEMENTS.add(Configuration.SYSTEM_STATUS_CHECKER_TYPE_NAME);
70         VERSIONED_ELEMENTS.add(Configuration.DEVICE_METRICS_COLLECTOR_TYPE_NAME);
71         VERSIONED_ELEMENTS.add(Configuration.MULTI_PRE_TARGET_PREPARER_TYPE_NAME);
72         VERSIONED_ELEMENTS.add(Configuration.MULTI_PREPARER_TYPE_NAME);
73         VERSIONED_ELEMENTS.add(Configuration.TARGET_PREPARER_TYPE_NAME);
74         VERSIONED_ELEMENTS.add(Configuration.TEST_TYPE_NAME);
75     }
76 
77     /**
78      * Parse the args and creates a {@link IConfiguration} from it then dumps it to the result file.
79      */
parse(String[] args)80     public int parse(String[] args) {
81         // TODO: add some more checking
82         List<String> argList = new ArrayList<>(Arrays.asList(args));
83         DumpCmd cmd = DumpCmd.valueOf(argList.remove(0));
84         File resFile = new File(argList.remove(0));
85         SandboxConfigurationFactory factory = SandboxConfigurationFactory.getInstance();
86         PrintWriter pw = null;
87         try {
88             IConfiguration config =
89                     factory.createConfigurationFromArgs(argList.toArray(new String[0]), cmd);
90             if (DumpCmd.RUN_CONFIG.equals(cmd) || DumpCmd.TEST_MODE.equals(cmd)) {
91                 config.getCommandOptions().setShouldUseSandboxing(false);
92                 config.getConfigurationDescription().setSandboxed(true);
93                 // Don't use replication in the sandbox
94                 config.getCommandOptions().setReplicateSetup(false);
95                 // Set the reporter
96                 ITestInvocationListener reporter = null;
97                 if (getSandboxOptions(config).shouldUseProtoReporter()) {
98                     reporter = new StreamProtoResultReporter();
99                 } else {
100                     reporter = new SubprocessResultsReporter();
101                     ((SubprocessResultsReporter) reporter).setOutputTestLog(true);
102                 }
103                 config.setTestInvocationListener(reporter);
104                 // Set log level for sandbox
105                 ILeveledLogOutput logger = config.getLogOutput();
106                 logger.setLogLevel(LogLevel.VERBOSE);
107                 if (logger instanceof FileLogger) {
108                     // Ensure we get the stdout logging in FileLogger case.
109                     ((FileLogger) logger).setLogLevelDisplay(LogLevel.VERBOSE);
110                 }
111 
112                 ILogSaver logSaver = config.getLogSaver();
113                 if (logSaver instanceof FileSystemLogSaver) {
114                     // Send the files directly, the parent will take care of compression if needed
115                     ((FileSystemLogSaver) logSaver).setCompressFiles(false);
116                 }
117 
118                 // Ensure in special conditions (placeholder devices) we can still allocate.
119                 secureDeviceAllocation(config);
120 
121                 // Mark as subprocess
122                 config.getCommandOptions()
123                         .getInvocationData()
124                         .put(SubprocessTfLauncher.SUBPROCESS_TAG_NAME, "true");
125             }
126             if (DumpCmd.TEST_MODE.equals(cmd)) {
127                 // We allow one more layer of sandbox to be generated
128                 config.getCommandOptions().setShouldUseSandboxing(true);
129                 config.getConfigurationDescription().setSandboxed(false);
130                 // Ensure we turn off test mode afterward to avoid infinite sandboxing
131                 config.getCommandOptions().setUseSandboxTestMode(false);
132             }
133             pw = new PrintWriter(resFile);
134             if (DumpCmd.NON_VERSIONED_CONFIG.equals(cmd)) {
135                 // Remove elements that are versioned.
136                 config.dumpXml(
137                         pw,
138                         new ArrayList<>(VERSIONED_ELEMENTS),
139                         true, /* Don't print unchanged options */
140                         false);
141             } else {
142                 // FULL_XML in that case.
143                 config.dumpXml(pw);
144             }
145         } catch (ConfigurationException | IOException e) {
146             e.printStackTrace();
147             return 1;
148         } finally {
149             StreamUtil.close(pw);
150         }
151         return 0;
152     }
153 
main(final String[] mainArgs)154     public static void main(final String[] mainArgs) {
155         try {
156             GlobalConfiguration.createGlobalConfiguration(new String[] {});
157         } catch (ConfigurationException e) {
158             e.printStackTrace();
159             System.exit(1);
160         }
161         SandboxConfigDump configDump = new SandboxConfigDump();
162         int code = configDump.parse(mainArgs);
163         System.exit(code);
164     }
165 
getSandboxOptions(IConfiguration config)166     private SandboxOptions getSandboxOptions(IConfiguration config) {
167         return (SandboxOptions)
168                 config.getConfigurationObject(Configuration.SANBOX_OPTIONS_TYPE_NAME);
169     }
170 
secureDeviceAllocation(IConfiguration config)171     private void secureDeviceAllocation(IConfiguration config) {
172         for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
173             IDeviceSelection requirements = deviceConfig.getDeviceRequirements();
174             if (requirements.nullDeviceRequested()
175                     || requirements.tcpDeviceRequested()
176                     || requirements.gceDeviceRequested()) {
177                 // Reset serials, ensure any null/tcp/gce-device can be selected.
178                 requirements.setSerial();
179             }
180         }
181     }
182 }
183