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         return createConfigurationFromArgs(args, keyStoreClient, sandbox, runUtil, null, false);
114     }
115 
116     /**
117      * Create a {@link IConfiguration} based on the command line and sandbox provided.
118      *
119      * @param args the command line for the run.
120      * @param keyStoreClient the {@link IKeyStoreClient} where to load the key from.
121      * @param sandbox the {@link ISandbox} used for the run.
122      * @param runUtil the {@link IRunUtil} to run commands.
123      * @return a {@link IConfiguration} valid for the sandbox.
124      * @throws ConfigurationException
125      */
createConfigurationFromArgs( String[] args, IKeyStoreClient keyStoreClient, ISandbox sandbox, IRunUtil runUtil, File globalConfig, boolean skipJavaCheck)126     public IConfiguration createConfigurationFromArgs(
127             String[] args,
128             IKeyStoreClient keyStoreClient,
129             ISandbox sandbox,
130             IRunUtil runUtil,
131             File globalConfig,
132             boolean skipJavaCheck)
133             throws ConfigurationException {
134         IConfiguration config = null;
135         File xmlConfig = null;
136         try {
137             runUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE);
138             // Dump the NON_VERSIONED part of the configuration against the current TF and not the
139             // sandboxed environment.
140             if (globalConfig == null) {
141                 globalConfig = SandboxConfigUtil.dumpFilteredGlobalConfig(new HashSet<>());
142             }
143             xmlConfig =
144                     SandboxConfigUtil.dumpConfigForVersion(
145                             createClasspath(),
146                             runUtil,
147                             args,
148                             DumpCmd.NON_VERSIONED_CONFIG,
149                             globalConfig,
150                             skipJavaCheck);
151             // Get the non version part of the configuration in order to do proper allocation
152             // of devices and such.
153             config =
154                     super.createConfigurationFromArgs(
155                             new String[] {xmlConfig.getAbsolutePath()}, null, keyStoreClient);
156             // Reset the command line to the original one.
157             config.setCommandLine(args);
158             config.setConfigurationObject(Configuration.SANDBOX_TYPE_NAME, sandbox);
159         } catch (SandboxConfigurationException e) {
160             CLog.w("Using thin launcher as fallback");
161             // Handle the thin launcher mode: Configuration does not exists in parent version yet.
162             config = sandbox.createThinLauncherConfig(args, keyStoreClient, runUtil, globalConfig);
163             if (config == null) {
164                 // Rethrow the original exception.
165                 CLog.e(e);
166                 throw e;
167             }
168         } catch (IOException e) {
169             CLog.e(e);
170             throw new ConfigurationException("Failed to dump global config.", e);
171         } catch (ConfigurationException e) {
172             CLog.e(e);
173             throw e;
174         } finally {
175             if (config == null) {
176                 // In case of error, tear down the sandbox.
177                 sandbox.tearDown();
178             }
179             FileUtil.deleteFile(globalConfig);
180             FileUtil.deleteFile(xmlConfig);
181         }
182         return config;
183     }
184 
getKeyStoreClient()185     private IKeyStoreClient getKeyStoreClient() {
186         try {
187             IKeyStoreFactory f = GlobalConfiguration.getInstance().getKeyStoreFactory();
188             if (f != null) {
189                 try {
190                     return f.createKeyStoreClient();
191                 } catch (KeyStoreException e) {
192                     CLog.e("Failed to create key store client");
193                     CLog.e(e);
194                 }
195             }
196         } catch (IllegalStateException e) {
197             CLog.w("Global configuration has not been created, failed to get keystore");
198             CLog.e(e);
199         }
200         return null;
201     }
202 
203     /** Returns the classpath of the current running Tradefed. */
createClasspath()204     private String createClasspath() throws ConfigurationException {
205         // Get the classpath property.
206         String classpathStr = System.getProperty("java.class.path");
207         if (classpathStr == null) {
208             throw new ConfigurationException(
209                     "Could not find the classpath property: java.class.path");
210         }
211         return classpathStr;
212     }
213 
214     private class RunSandboxConfigurationFactory extends SandboxConfigurationFactory {
215 
216         private DumpCmd mCommand;
217 
RunSandboxConfigurationFactory(DumpCmd command)218         RunSandboxConfigurationFactory(DumpCmd command) {
219             mCommand = command;
220         }
221 
222         @Override
createConfigurationDef(String name)223         protected ConfigurationDef createConfigurationDef(String name) {
224             return new ConfigurationDef(name) {
225                 @Override
226                 protected void checkRejectedObjects(
227                         Map<String, String> rejectedObjects, Throwable cause)
228                         throws ClassNotFoundConfigurationException {
229                     if (mCommand.equals(DumpCmd.RUN_CONFIG) || mCommand.equals(DumpCmd.TEST_MODE)) {
230                         Map<String, String> copyRejected = new HashMap<>();
231                         for (Entry<String, String> item : rejectedObjects.entrySet()) {
232                             if (SandboxConfigDump.VERSIONED_ELEMENTS.contains(item.getValue())) {
233                                 copyRejected.put(item.getKey(), item.getValue());
234                             }
235                         }
236                         super.checkRejectedObjects(copyRejected, cause);
237                     } else {
238                         super.checkRejectedObjects(rejectedObjects, cause);
239                     }
240                 }
241 
242                 @Override
243                 protected void injectOptions(IConfiguration config, List<OptionDef> optionList)
244                         throws ConfigurationException {
245                     List<OptionDef> individualAttempt = new ArrayList<>();
246                     if (mCommand.equals(DumpCmd.RUN_CONFIG) || mCommand.equals(DumpCmd.TEST_MODE)) {
247                         individualAttempt =
248                                 optionList
249                                         .stream()
250                                         .filter(
251                                                 o ->
252                                                         (o.applicableObjectType != null
253                                                                 && !OPTION_IGNORED_ELEMENTS
254                                                                         .contains(
255                                                                                 o.applicableObjectType)))
256                                         .collect(Collectors.toList());
257                         optionList.removeAll(individualAttempt);
258                     }
259                     super.injectOptions(config, optionList);
260                     // Try individually each "filtered-option", they will not stop the run if they
261                     // cannot be set since they are not part of the versioned process.
262                     for (OptionDef item : individualAttempt) {
263                         List<OptionDef> tmpList = Arrays.asList(item);
264                         try {
265                             super.injectOptions(config, tmpList);
266                         } catch (ConfigurationException e) {
267                             // Ignore
268                         }
269                     }
270                 }
271             };
272         }
273     }
274 }
275