1 /*
2  * Copyright (C) 2012 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 
17 package com.android.tradefed.config;
18 
19 import com.android.tradefed.build.BuildRetrievalError;
20 import com.android.tradefed.command.CommandScheduler;
21 import com.android.tradefed.command.ICommandScheduler;
22 import com.android.tradefed.config.gcs.GCSConfigurationFactory;
23 import com.android.tradefed.device.DeviceManager;
24 import com.android.tradefed.device.DeviceSelectionOptions;
25 import com.android.tradefed.device.IDeviceManager;
26 import com.android.tradefed.device.IDeviceMonitor;
27 import com.android.tradefed.device.IDeviceSelection;
28 import com.android.tradefed.device.IMultiDeviceRecovery;
29 import com.android.tradefed.host.HostOptions;
30 import com.android.tradefed.host.IHostOptions;
31 import com.android.tradefed.host.IHostResourceManager;
32 import com.android.tradefed.host.LocalHostResourceManager;
33 import com.android.tradefed.invoker.shard.IShardHelper;
34 import com.android.tradefed.invoker.shard.StrictShardHelper;
35 import com.android.tradefed.log.ITerribleFailureHandler;
36 import com.android.tradefed.log.LogUtil.CLog;
37 import com.android.tradefed.sandbox.ISandboxFactory;
38 import com.android.tradefed.sandbox.TradefedSandboxFactory;
39 import com.android.tradefed.util.ArrayUtil;
40 import com.android.tradefed.util.FileUtil;
41 import com.android.tradefed.util.MultiMap;
42 import com.android.tradefed.util.hostmetric.IHostMonitor;
43 import com.android.tradefed.util.keystore.IKeyStoreFactory;
44 import com.android.tradefed.util.keystore.StubKeyStoreFactory;
45 
46 import com.google.common.annotations.VisibleForTesting;
47 
48 import org.kxml2.io.KXmlSerializer;
49 
50 import java.io.File;
51 import java.io.IOException;
52 import java.io.PrintStream;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collection;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.LinkedHashMap;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Set;
62 import java.util.regex.Pattern;
63 
64 /**
65  * An {@link IGlobalConfiguration} implementation that stores the loaded config objects in a map
66  */
67 public class GlobalConfiguration implements IGlobalConfiguration {
68     // type names for built in configuration objects
69     public static final String DEVICE_MONITOR_TYPE_NAME = "device_monitor";
70     public static final String HOST_MONITOR_TYPE_NAME = "host_monitor";
71     public static final String DEVICE_MANAGER_TYPE_NAME = "device_manager";
72     public static final String WTF_HANDLER_TYPE_NAME = "wtf_handler";
73     public static final String HOST_OPTIONS_TYPE_NAME = "host_options";
74     public static final String HOST_RESOURCE_MANAGER_TYPE_NAME = "host_resource_manager";
75     public static final String DEVICE_REQUIREMENTS_TYPE_NAME = "device_requirements";
76     public static final String SCHEDULER_TYPE_NAME = "command_scheduler";
77     public static final String MULTI_DEVICE_RECOVERY_TYPE_NAME = "multi_device_recovery";
78     public static final String KEY_STORE_TYPE_NAME = "key_store";
79     public static final String SHARDING_STRATEGY_TYPE_NAME = "sharding_strategy";
80     public static final String GLOBAL_CONFIG_SERVER = "global_config_server";
81     public static final String SANDBOX_FACTORY_TYPE_NAME = "sandbox_factory";
82 
83     public static final String GLOBAL_CONFIG_VARIABLE = "TF_GLOBAL_CONFIG";
84     public static final String GLOBAL_CONFIG_SERVER_CONFIG_VARIABLE =
85             "TF_GLOBAL_CONFIG_SERVER_CONFIG";
86     private static final String GLOBAL_CONFIG_FILENAME = "tf_global_config.xml";
87 
88     private static Map<String, ObjTypeInfo> sObjTypeMap = null;
89     private static IGlobalConfiguration sInstance = null;
90     private static final Object sInstanceLock = new Object();
91 
92     // Empty embedded configuration available by default
93     private static final String DEFAULT_EMPTY_CONFIG_NAME = "empty";
94 
95     // Configurations to be passed to subprocess: Typical object that are representing the host
96     // level and the subprocess should follow too.
97     private static final String[] CONFIGS_FOR_SUBPROCESS_ALLOW_LIST =
98             new String[] {
99                 DEVICE_MANAGER_TYPE_NAME,
100                 KEY_STORE_TYPE_NAME,
101                 HOST_OPTIONS_TYPE_NAME,
102                 "android-build"
103             };
104 
105     /** Mapping of config object type name to config objects. */
106     private Map<String, List<Object>> mConfigMap;
107     private MultiMap<String, String> mOptionMap;
108     private String[] mOriginalArgs;
109     private final String mName;
110     private final String mDescription;
111     private IConfigurationFactory mConfigFactory = null;
112 
113     /**
114      * Returns a reference to the singleton {@link GlobalConfiguration} instance for this TF
115      * instance.
116      *
117      * @throws IllegalStateException if {@link #createGlobalConfiguration(String[])} has not
118      *         already been called.
119      */
getInstance()120     public static IGlobalConfiguration getInstance() {
121         if (sInstance == null) {
122             throw new IllegalStateException("GlobalConfiguration has not yet been initialized!");
123         }
124         return sInstance;
125     }
126 
127     /**
128      * Returns a reference to the singleton {@link DeviceManager} instance for this TF
129      * instance.
130      *
131      * @throws IllegalStateException if {@link #createGlobalConfiguration(String[])} has not
132      *         already been called.
133      */
getDeviceManagerInstance()134     public static IDeviceManager getDeviceManagerInstance() {
135         if (sInstance == null) {
136             throw new IllegalStateException("GlobalConfiguration has not yet been initialized!");
137         }
138         return sInstance.getDeviceManager();
139     }
140 
getHostMonitorInstances()141     public static List<IHostMonitor> getHostMonitorInstances() {
142         if (sInstance == null) {
143             throw new IllegalStateException("GlobalConfiguration has not yet been initialized!");
144         }
145         return sInstance.getHostMonitors();
146     }
147 
148     /**
149      * Sets up the {@link GlobalConfiguration} singleton for this TF instance.  Must be called
150      * once and only once, before anything attempts to call {@link #getInstance()}
151      *
152      * @throws IllegalStateException if called more than once
153      */
createGlobalConfiguration(String[] args)154     public static List<String> createGlobalConfiguration(String[] args)
155             throws ConfigurationException {
156         synchronized (sInstanceLock) {
157             if (sInstance != null) {
158                 throw new IllegalStateException("GlobalConfiguration is already initialized!");
159             }
160             List<String> nonGlobalArgs = new ArrayList<String>(args.length);
161             List<String> nonConfigServerArgs = new ArrayList<String>(args.length);
162             IConfigurationServer globalConfigServer =
163                     createGlobalConfigServer(args, nonConfigServerArgs);
164             if (globalConfigServer == null) {
165                 String path = getGlobalConfigPath();
166                 String[] arrayArgs = ArrayUtil.buildArray(new String[] {path}, args);
167                 IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
168                 sInstance =
169                         configFactory.createGlobalConfigurationFromArgs(arrayArgs, nonGlobalArgs);
170                 ((GlobalConfiguration) sInstance).mOriginalArgs = arrayArgs;
171             } else {
172                 String currentHostConfig = globalConfigServer.getCurrentHostConfig();
173                 IConfigurationFactory configFactory =
174                         GCSConfigurationFactory.getInstance(globalConfigServer);
175                 String[] arrayArgs =
176                         ArrayUtil.buildArray(
177                                 new String[] {currentHostConfig},
178                                 nonConfigServerArgs.toArray(new String[0]));
179                 sInstance =
180                         configFactory.createGlobalConfigurationFromArgs(arrayArgs, nonGlobalArgs);
181                 // Keep the original args, later if we want to clone the global config,
182                 // we will reuse GCSConfigurationFactory to download the config again.
183                 ((GlobalConfiguration) sInstance).mOriginalArgs = arrayArgs;
184             }
185             // Validate that madatory options have been set
186             sInstance.validateOptions();
187 
188             return nonGlobalArgs;
189         }
190     }
191 
192     /**
193      * Returns the path to a global config, if one exists, or <code>null</code> if none could be
194      * found.
195      * <p />
196      * Search locations, in decreasing order of precedence
197      * <ol>
198      *   <li><code>$TF_GLOBAL_CONFIG</code> environment variable</li>
199      *   <li><code>tf_global_config.xml</code> file in $PWD</li>
200      *   <li>(FIXME) <code>tf_global_config.xml</code> file in dir where <code>tradefed.sh</code>
201      *       lives</li>
202      * </ol>
203      */
getGlobalConfigPath()204     private static String getGlobalConfigPath() {
205         String path = System.getenv(GLOBAL_CONFIG_VARIABLE);
206         if (path != null) {
207             // don't actually check for accessibility here, since the variable might be specifying
208             // a java resource rather than a filename.  Even so, this can help the user figure out
209             // which global config (if any) was picked up by TF.
210             System.out.format(
211                     "Attempting to use global config \"%s\" from variable $%s.\n",
212                     path, GLOBAL_CONFIG_VARIABLE);
213             return path;
214         }
215 
216         File file = new File(GLOBAL_CONFIG_FILENAME);
217         if (file.exists()) {
218             path = file.getPath();
219             System.out.format("Attempting to use autodetected global config \"%s\".\n", path);
220             return path;
221         }
222 
223         // FIXME: search in tradefed.sh launch dir (or classpath?)
224 
225         // Use default empty known global config
226         return DEFAULT_EMPTY_CONFIG_NAME;
227     }
228 
229     /**
230      * Returns an {@link IConfigurationServer}, if one exists, or <code>null</code> if none could be
231      * found.
232      *
233      * @param args for config server
234      * @param nonConfigServerArgs a list which will be populated with the arguments that weren't
235      *     processed as global arguments
236      * @return an {@link IConfigurationServer}
237      * @throws ConfigurationException
238      */
239     @VisibleForTesting
createGlobalConfigServer( String[] args, List<String> nonConfigServerArgs)240     static IConfigurationServer createGlobalConfigServer(
241             String[] args, List<String> nonConfigServerArgs) throws ConfigurationException {
242         String path = System.getenv(GLOBAL_CONFIG_SERVER_CONFIG_VARIABLE);
243         if (path == null) {
244             // No config server, should use config files.
245             nonConfigServerArgs.addAll(Arrays.asList(args));
246             return null;
247         } else {
248             System.out.format("Use global config server config %s.\n", path);
249         }
250         IConfigurationServer configServer = null;
251         IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
252         IGlobalConfiguration configServerConfig =
253                 configFactory.createGlobalConfigurationFromArgs(
254                         ArrayUtil.buildArray(new String[] {path}, args), nonConfigServerArgs);
255         configServer = configServerConfig.getGlobalConfigServer();
256         return configServer;
257     }
258 
259     /**
260      * Container struct for built-in config object type
261      */
262     private static class ObjTypeInfo {
263         final Class<?> mExpectedType;
264         /** true if a list (ie many objects in a single config) are supported for this type */
265         final boolean mIsListSupported;
266 
ObjTypeInfo(Class<?> expectedType, boolean isList)267         ObjTypeInfo(Class<?> expectedType, boolean isList) {
268             mExpectedType = expectedType;
269             mIsListSupported = isList;
270         }
271     }
272 
273     /**
274      * Determine if given config object type name is a built in object
275      *
276      * @param typeName the config object type name
277      * @return <code>true</code> if name is a built in object type
278      */
isBuiltInObjType(String typeName)279     static boolean isBuiltInObjType(String typeName) {
280         return getObjTypeMap().containsKey(typeName);
281     }
282 
getObjTypeMap()283     private static synchronized Map<String, ObjTypeInfo> getObjTypeMap() {
284         if (sObjTypeMap == null) {
285             sObjTypeMap = new HashMap<String, ObjTypeInfo>();
286             sObjTypeMap.put(HOST_OPTIONS_TYPE_NAME, new ObjTypeInfo(IHostOptions.class, false));
287             sObjTypeMap.put(
288                     HOST_RESOURCE_MANAGER_TYPE_NAME,
289                     new ObjTypeInfo(IHostResourceManager.class, false));
290             sObjTypeMap.put(DEVICE_MONITOR_TYPE_NAME, new ObjTypeInfo(IDeviceMonitor.class, true));
291             sObjTypeMap.put(HOST_MONITOR_TYPE_NAME, new ObjTypeInfo(IHostMonitor.class, true));
292             sObjTypeMap.put(DEVICE_MANAGER_TYPE_NAME, new ObjTypeInfo(IDeviceManager.class, false));
293             sObjTypeMap.put(DEVICE_REQUIREMENTS_TYPE_NAME, new ObjTypeInfo(IDeviceSelection.class,
294                     false));
295             sObjTypeMap.put(WTF_HANDLER_TYPE_NAME,
296                     new ObjTypeInfo(ITerribleFailureHandler.class, false));
297             sObjTypeMap.put(SCHEDULER_TYPE_NAME, new ObjTypeInfo(ICommandScheduler.class, false));
298             sObjTypeMap.put(
299                     MULTI_DEVICE_RECOVERY_TYPE_NAME,
300                     new ObjTypeInfo(IMultiDeviceRecovery.class, true));
301             sObjTypeMap.put(KEY_STORE_TYPE_NAME, new ObjTypeInfo(IKeyStoreFactory.class, false));
302             sObjTypeMap.put(
303                     SHARDING_STRATEGY_TYPE_NAME, new ObjTypeInfo(IShardHelper.class, false));
304             sObjTypeMap.put(
305                     GLOBAL_CONFIG_SERVER, new ObjTypeInfo(IConfigurationServer.class, false));
306             sObjTypeMap.put(
307                     SANDBOX_FACTORY_TYPE_NAME, new ObjTypeInfo(ISandboxFactory.class, false));
308         }
309         return sObjTypeMap;
310     }
311 
312     /**
313      * Creates a {@link GlobalConfiguration} with default config objects
314      */
GlobalConfiguration(String name, String description)315     GlobalConfiguration(String name, String description) {
316         mName = name;
317         mDescription = description;
318         mConfigMap = new LinkedHashMap<String, List<Object>>();
319         mOptionMap = new MultiMap<String, String>();
320         mOriginalArgs = new String[] {"empty"};
321         setHostOptions(new HostOptions());
322         setHostResourceManager(new LocalHostResourceManager());
323         setDeviceRequirements(new DeviceSelectionOptions());
324         setDeviceManager(new DeviceManager());
325         setCommandScheduler(new CommandScheduler());
326         setKeyStoreFactory(new StubKeyStoreFactory());
327         setShardingStrategy(new StrictShardHelper());
328         setSandboxFactory(new TradefedSandboxFactory());
329     }
330 
331     /** {@inheritDoc} */
332     @Override
setOriginalConfig(String config)333     public void setOriginalConfig(String config) {
334         mOriginalArgs = new String[] {config};
335     }
336 
337     /** {@inheritDoc} */
338     @Override
setup()339     public void setup() throws ConfigurationException {
340         getHostResourceManager().setup();
341     }
342 
343     /** {@inheritDoc} */
344     @Override
cleanup()345     public void cleanup() {
346         getHostResourceManager().cleanup();
347     }
348 
349     /**
350      * @return the name of this {@link Configuration}
351      */
getName()352     public String getName() {
353         return mName;
354     }
355 
356     /**
357      * @return a short user readable description this {@link Configuration}
358      */
getDescription()359     public String getDescription() {
360         return mDescription;
361     }
362 
363     /**
364      * {@inheritDoc}
365      */
366     @Override
getHostOptions()367     public IHostOptions getHostOptions() {
368         return (IHostOptions) getConfigurationObject(HOST_OPTIONS_TYPE_NAME);
369     }
370 
371     /** {@inheritDoc} */
372     @Override
getHostResourceManager()373     public IHostResourceManager getHostResourceManager() {
374         return (IHostResourceManager) getConfigurationObject(HOST_RESOURCE_MANAGER_TYPE_NAME);
375     }
376 
377     /** {@inheritDoc} */
378     @Override
379     @SuppressWarnings("unchecked")
getDeviceMonitors()380     public List<IDeviceMonitor> getDeviceMonitors() {
381         return (List<IDeviceMonitor>) getConfigurationObjectList(DEVICE_MONITOR_TYPE_NAME);
382     }
383 
384     @Override
getGlobalConfigServer()385     public IConfigurationServer getGlobalConfigServer() {
386         return (IConfigurationServer) getConfigurationObject(GLOBAL_CONFIG_SERVER);
387     }
388 
389     /** {@inheritDoc} */
390     @Override
391     @SuppressWarnings("unchecked")
getHostMonitors()392     public List<IHostMonitor> getHostMonitors() {
393         return (List<IHostMonitor>) getConfigurationObjectList(HOST_MONITOR_TYPE_NAME);
394     }
395 
396     /**
397      * {@inheritDoc}
398      */
399     @Override
getWtfHandler()400     public ITerribleFailureHandler getWtfHandler() {
401         return (ITerribleFailureHandler) getConfigurationObject(WTF_HANDLER_TYPE_NAME);
402     }
403 
404     /**
405      * {@inheritDoc}
406      */
407     @Override
getKeyStoreFactory()408     public IKeyStoreFactory getKeyStoreFactory() {
409         return (IKeyStoreFactory) getConfigurationObject(KEY_STORE_TYPE_NAME);
410     }
411 
412     /** {@inheritDoc} */
413     @Override
getShardingStrategy()414     public IShardHelper getShardingStrategy() {
415         return (IShardHelper) getConfigurationObject(SHARDING_STRATEGY_TYPE_NAME);
416     }
417 
418     /** {@inheritDoc} */
419     @Override
getDeviceManager()420     public IDeviceManager getDeviceManager() {
421         return (IDeviceManager) getConfigurationObject(DEVICE_MANAGER_TYPE_NAME);
422     }
423 
424     /** {@inheritDoc} */
425     @Override
getSandboxFactory()426     public ISandboxFactory getSandboxFactory() {
427         return (ISandboxFactory) getConfigurationObject(SANDBOX_FACTORY_TYPE_NAME);
428     }
429 
430     /** {@inheritDoc} */
431     @Override
getDeviceRequirements()432     public IDeviceSelection getDeviceRequirements() {
433         return (IDeviceSelection) getConfigurationObject(DEVICE_REQUIREMENTS_TYPE_NAME);
434     }
435 
436     /**
437      * {@inheritDoc}
438      */
439     @Override
getCommandScheduler()440     public ICommandScheduler getCommandScheduler() {
441         return (ICommandScheduler)getConfigurationObject(SCHEDULER_TYPE_NAME);
442     }
443 
444     /**
445      * {@inheritDoc}
446      */
447     @Override
448     @SuppressWarnings("unchecked")
getMultiDeviceRecoveryHandlers()449     public List<IMultiDeviceRecovery> getMultiDeviceRecoveryHandlers() {
450         return (List<IMultiDeviceRecovery>)getConfigurationObjectList(
451                 MULTI_DEVICE_RECOVERY_TYPE_NAME);
452     }
453 
454     /**
455      * Internal helper to get the list of config object
456      */
getConfigurationObjectList(String typeName)457     private List<?> getConfigurationObjectList(String typeName) {
458         return mConfigMap.get(typeName);
459     }
460 
461     /**
462      * {@inheritDoc}
463      */
464     @Override
getConfigurationObject(String typeName)465     public Object getConfigurationObject(String typeName) {
466         List<?> configObjects = getConfigurationObjectList(typeName);
467         if (configObjects == null) {
468             return null;
469         }
470         ObjTypeInfo typeInfo = getObjTypeMap().get(typeName);
471         if (typeInfo != null && typeInfo.mIsListSupported) {
472             throw new IllegalStateException(
473                     String.format(
474                             "Wrong method call for type %s. Used getConfigurationObject() for a "
475                                     + "config object that is stored as a list",
476                             typeName));
477         }
478         if (configObjects.size() != 1) {
479             throw new IllegalStateException(String.format(
480                     "Attempted to retrieve single object for %s, but %d are present",
481                     typeName, configObjects.size()));
482         }
483         return configObjects.get(0);
484     }
485 
486     /**
487      * Return a copy of all config objects
488      */
getAllConfigurationObjects()489     private Collection<Object> getAllConfigurationObjects() {
490         Collection<Object> objectsCopy = new ArrayList<Object>();
491         for (List<Object> objectList : mConfigMap.values()) {
492             objectsCopy.addAll(objectList);
493         }
494         return objectsCopy;
495     }
496 
497     /**
498      * {@inheritDoc}
499      */
500     @Override
injectOptionValue(String optionName, String optionValue)501     public void injectOptionValue(String optionName, String optionValue)
502             throws ConfigurationException {
503         injectOptionValue(optionName, null, optionValue);
504     }
505 
506     /**
507      * {@inheritDoc}
508      */
509     @Override
injectOptionValue(String optionName, String optionKey, String optionValue)510     public void injectOptionValue(String optionName, String optionKey, String optionValue)
511             throws ConfigurationException {
512         OptionSetter optionSetter = new OptionSetter(getAllConfigurationObjects());
513         optionSetter.setOptionValue(optionName, optionKey, optionValue);
514 
515         if (optionKey != null) {
516             mOptionMap.put(optionName, optionKey + "=" + optionValue);
517         } else {
518             mOptionMap.put(optionName, optionValue);
519         }
520     }
521 
522     /**
523      * {@inheritDoc}
524      */
525     @Override
getOptionValues(String optionName)526     public List<String> getOptionValues(String optionName) {
527         return mOptionMap.get(optionName);
528     }
529 
530     /**
531      * {@inheritDoc}
532      */
533     @Override
setHostOptions(IHostOptions hostOptions)534     public void setHostOptions(IHostOptions hostOptions) {
535         setConfigurationObjectNoThrow(HOST_OPTIONS_TYPE_NAME, hostOptions);
536     }
537 
538     /** {@inheritDoc} */
539     @Override
setHostResourceManager(IHostResourceManager hostResourceManager)540     public void setHostResourceManager(IHostResourceManager hostResourceManager) {
541         setConfigurationObjectNoThrow(HOST_RESOURCE_MANAGER_TYPE_NAME, hostResourceManager);
542     }
543 
544     /**
545      * {@inheritDoc}
546      */
547     @Override
setDeviceMonitor(IDeviceMonitor monitor)548     public void setDeviceMonitor(IDeviceMonitor monitor) {
549         setConfigurationObjectNoThrow(DEVICE_MONITOR_TYPE_NAME, monitor);
550     }
551 
552     /** {@inheritDoc} */
553     @Override
setHostMonitors(List<IHostMonitor> hostMonitors)554     public void setHostMonitors(List<IHostMonitor> hostMonitors) {
555         setConfigurationObjectListNoThrow(HOST_MONITOR_TYPE_NAME, hostMonitors);
556     }
557 
558     /**
559      * {@inheritDoc}
560      */
561     @Override
setWtfHandler(ITerribleFailureHandler wtfHandler)562     public void setWtfHandler(ITerribleFailureHandler wtfHandler) {
563         setConfigurationObjectNoThrow(WTF_HANDLER_TYPE_NAME, wtfHandler);
564     }
565 
566     /**
567      * {@inheritDoc}
568      */
569     @Override
setKeyStoreFactory(IKeyStoreFactory factory)570     public void setKeyStoreFactory(IKeyStoreFactory factory) {
571         setConfigurationObjectNoThrow(KEY_STORE_TYPE_NAME, factory);
572     }
573 
574     /** {@inheritDoc} */
575     @Override
setShardingStrategy(IShardHelper sharding)576     public void setShardingStrategy(IShardHelper sharding) {
577         setConfigurationObjectNoThrow(SHARDING_STRATEGY_TYPE_NAME, sharding);
578     }
579 
580     /** {@inheritDoc} */
581     @Override
setDeviceManager(IDeviceManager manager)582     public void setDeviceManager(IDeviceManager manager) {
583         setConfigurationObjectNoThrow(DEVICE_MANAGER_TYPE_NAME, manager);
584     }
585 
586     /**
587      * {@inheritDoc}
588      */
589     @Override
setDeviceRequirements(IDeviceSelection devRequirements)590     public void setDeviceRequirements(IDeviceSelection devRequirements) {
591         setConfigurationObjectNoThrow(DEVICE_REQUIREMENTS_TYPE_NAME, devRequirements);
592     }
593 
594     /**
595      * {@inheritDoc}
596      */
597     @Override
setCommandScheduler(ICommandScheduler scheduler)598     public void setCommandScheduler(ICommandScheduler scheduler) {
599         setConfigurationObjectNoThrow(SCHEDULER_TYPE_NAME, scheduler);
600     }
601 
602     @Override
setSandboxFactory(ISandboxFactory factory)603     public void setSandboxFactory(ISandboxFactory factory) {
604         setConfigurationObjectNoThrow(SANDBOX_FACTORY_TYPE_NAME, factory);
605     }
606 
607     /** {@inheritDoc} */
608     @Override
setConfigurationObject(String typeName, Object configObject)609     public void setConfigurationObject(String typeName, Object configObject)
610             throws ConfigurationException {
611         if (configObject == null) {
612             throw new IllegalArgumentException("configObject cannot be null");
613         }
614         mConfigMap.remove(typeName);
615         addObject(typeName, configObject);
616     }
617 
618     /**
619      * {@inheritDoc}
620      */
621     @Override
setConfigurationObjectList(String typeName, List<?> configList)622     public void setConfigurationObjectList(String typeName, List<?> configList)
623             throws ConfigurationException {
624         if (configList == null) {
625             throw new IllegalArgumentException("configList cannot be null");
626         }
627         mConfigMap.remove(typeName);
628         for (Object configObject : configList) {
629             addObject(typeName, configObject);
630         }
631     }
632 
633     /**
634      * A wrapper around {@link #setConfigurationObjectList(String, List)} that will not throw {@link
635      * ConfigurationException}.
636      *
637      * <p>Intended to be used in cases where its guaranteed that <var>configObject</var> is the
638      * correct type
639      */
setConfigurationObjectListNoThrow(String typeName, List<?> configList)640     private void setConfigurationObjectListNoThrow(String typeName, List<?> configList) {
641         try {
642             setConfigurationObjectList(typeName, configList);
643         } catch (ConfigurationException e) {
644             // should never happen
645             throw new IllegalArgumentException(e);
646         }
647     }
648 
649     /**
650      * Adds a loaded object to this configuration.
651      *
652      * @param typeName the unique object type name of the configuration object
653      * @param configObject the configuration object
654      * @throws ConfigurationException if object was not the correct type
655      */
addObject(String typeName, Object configObject)656     private void addObject(String typeName, Object configObject) throws ConfigurationException {
657         List<Object> objList = mConfigMap.get(typeName);
658         if (objList == null) {
659             objList = new ArrayList<Object>(1);
660             mConfigMap.put(typeName, objList);
661         }
662         ObjTypeInfo typeInfo = getObjTypeMap().get(typeName);
663         if (typeInfo != null && !typeInfo.mExpectedType.isInstance(configObject)) {
664             throw new ConfigurationException(String.format(
665                     "The config object %s is not the correct type. Expected %s, received %s",
666                     typeName, typeInfo.mExpectedType.getCanonicalName(),
667                     configObject.getClass().getCanonicalName()));
668         }
669         if (typeInfo != null && !typeInfo.mIsListSupported && objList.size() > 0) {
670             throw new ConfigurationException(String.format(
671                     "Only one config object allowed for %s, but multiple were specified.",
672                     typeName));
673         }
674         objList.add(configObject);
675     }
676 
677     /**
678      * A wrapper around {@link #setConfigurationObject(String, Object)} that will not throw
679      * {@link ConfigurationException}.
680      * <p/>
681      * Intended to be used in cases where its guaranteed that <var>configObject</var> is the
682      * correct type.
683      *
684      * @param typeName
685      * @param configObject
686      */
setConfigurationObjectNoThrow(String typeName, Object configObject)687     private void setConfigurationObjectNoThrow(String typeName, Object configObject) {
688         try {
689             setConfigurationObject(typeName, configObject);
690         } catch (ConfigurationException e) {
691             // should never happen
692             throw new IllegalArgumentException(e);
693         }
694     }
695 
696     /**
697      * {@inheritDoc}
698      */
699     @Override
setOptionsFromCommandLineArgs(List<String> listArgs)700     public List<String> setOptionsFromCommandLineArgs(List<String> listArgs)
701             throws ConfigurationException {
702         ArgsOptionParser parser = new ArgsOptionParser(getAllConfigurationObjects());
703         return parser.parse(listArgs);
704     }
705 
706     /**
707      * Outputs a command line usage help text for this configuration to given printStream.
708      *
709      * @param out the {@link PrintStream} to use.
710      * @throws ConfigurationException
711      */
printCommandUsage(boolean importantOnly, PrintStream out)712     public void printCommandUsage(boolean importantOnly, PrintStream out)
713             throws ConfigurationException {
714         out.println(String.format("'%s' configuration: %s", getName(), getDescription()));
715         out.println();
716         if (importantOnly) {
717             out.println("Printing help for only the important options. " +
718                     "To see help for all options, use the --help-all flag");
719             out.println();
720         }
721         for (Map.Entry<String, List<Object>> configObjectsEntry : mConfigMap.entrySet()) {
722             for (Object configObject : configObjectsEntry.getValue()) {
723                 String optionHelp = printOptionsForObject(importantOnly,
724                         configObjectsEntry.getKey(), configObject);
725                 // only print help for object if optionHelp is non zero length
726                 if (optionHelp.length() > 0) {
727                     String classAlias = "";
728                     if (configObject.getClass().isAnnotationPresent(OptionClass.class)) {
729                         final OptionClass classAnnotation = configObject.getClass().getAnnotation(
730                                 OptionClass.class);
731                         classAlias = String.format("'%s' ", classAnnotation.alias());
732                     }
733                     out.printf("  %s%s options:", classAlias, configObjectsEntry.getKey());
734                     out.println();
735                     out.print(optionHelp);
736                     out.println();
737                 }
738             }
739         }
740     }
741 
742     /**
743      * Prints out the available config options for given configuration object.
744      *
745      * @param importantOnly print only the important options
746      * @param objectTypeName the config object type name. Used to generate more descriptive error
747      *            messages
748      * @param configObject the config object
749      * @return a {@link String} of option help text
750      * @throws ConfigurationException
751      */
printOptionsForObject(boolean importantOnly, String objectTypeName, Object configObject)752     private String printOptionsForObject(boolean importantOnly, String objectTypeName,
753             Object configObject) throws ConfigurationException {
754         return ArgsOptionParser.getOptionHelp(importantOnly, configObject);
755     }
756 
757     /**
758      * {@inheritDoc}
759      */
760     @Override
validateOptions()761     public void validateOptions() throws ConfigurationException {
762         ArgsOptionParser argsParser = new ArgsOptionParser(getAllConfigurationObjects());
763         argsParser.validateMandatoryOptions();
764 
765         getHostOptions().validateOptions();
766 
767         CLog.d("Resolve and remote files from @Option");
768         // Setup and validate the GCS File paths, they will be deleted when TF ends
769         List<File> remoteFiles = new ArrayList<>();
770         try {
771             remoteFiles.addAll(argsParser.validateRemoteFilePath(new DynamicRemoteFileResolver()));
772         } catch (BuildRetrievalError e) {
773             throw new ConfigurationException(e.getMessage(), e);
774         }
775         remoteFiles.forEach(File::deleteOnExit);
776     }
777 
778     /** {@inheritDoc} */
779     @Override
cloneConfigWithFilter(String... allowlistConfigs)780     public File cloneConfigWithFilter(String... allowlistConfigs) throws IOException {
781         return cloneConfigWithFilter(new HashSet<>(), allowlistConfigs);
782     }
783 
784     /** {@inheritDoc} */
785     @Override
cloneConfigWithFilter(Set<String> exclusionPatterns, String... allowlistConfigs)786     public File cloneConfigWithFilter(Set<String> exclusionPatterns, String... allowlistConfigs)
787             throws IOException {
788         IConfigurationFactory configFactory = getConfigurationFactory();
789         IGlobalConfiguration copy = null;
790         try {
791             // Use a copy with default original options
792             copy =
793                     configFactory.createGlobalConfigurationFromArgs(
794                             mOriginalArgs, new ArrayList<>());
795         } catch (ConfigurationException e) {
796             throw new IOException(e);
797         }
798 
799         File filteredGlobalConfig = FileUtil.createTempFile("filtered_global_config", ".config");
800         KXmlSerializer serializer = ConfigurationUtil.createSerializer(filteredGlobalConfig);
801         serializer.startTag(null, ConfigurationUtil.CONFIGURATION_NAME);
802         if (allowlistConfigs == null || allowlistConfigs.length == 0) {
803             allowlistConfigs = CONFIGS_FOR_SUBPROCESS_ALLOW_LIST;
804         }
805         for (String config : allowlistConfigs) {
806             Object configObj = copy.getConfigurationObject(config);
807             if (configObj == null) {
808                 CLog.d("Object '%s' was not found in global config.", config);
809                 continue;
810             }
811             String name = configObj.getClass().getCanonicalName();
812             if (!shouldDump(name, exclusionPatterns)) {
813                 continue;
814             }
815             boolean isGenericObject = false;
816             if (getObjTypeMap().get(config) == null) {
817                 isGenericObject = true;
818             }
819             ConfigurationUtil.dumpClassToXml(
820                     serializer, config, configObj, isGenericObject, new ArrayList<>(), true, false);
821         }
822         serializer.endTag(null, ConfigurationUtil.CONFIGURATION_NAME);
823         serializer.endDocument();
824         return filteredGlobalConfig;
825     }
826 
827     /** {@inheritDoc} */
828     @Override
setConfigurationFactory(IConfigurationFactory configFactory)829     public void setConfigurationFactory(IConfigurationFactory configFactory) {
830         mConfigFactory = configFactory;
831     }
832 
833     @VisibleForTesting
getConfigurationFactory()834     protected IConfigurationFactory getConfigurationFactory() {
835         return mConfigFactory;
836     }
837 
shouldDump(String name, Set<String> patterns)838     private boolean shouldDump(String name, Set<String> patterns) {
839         for (String pattern : patterns) {
840             if (Pattern.matches(pattern, name)) {
841                 return false;
842             }
843         }
844         return true;
845     }
846 }
847