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