• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.compatibility.common.tradefed.targetprep;
17 
18 import com.android.annotations.VisibleForTesting;
19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
20 import com.android.compatibility.common.util.DynamicConfig;
21 import com.android.compatibility.common.util.DynamicConfigHandler;
22 import com.android.tradefed.build.IBuildInfo;
23 import com.android.tradefed.config.Option;
24 import com.android.tradefed.config.OptionClass;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.invoker.IInvocationContext;
28 import com.android.tradefed.log.LogUtil.CLog;
29 import com.android.tradefed.targetprep.BaseTargetPreparer;
30 import com.android.tradefed.targetprep.BuildError;
31 import com.android.tradefed.targetprep.ITargetCleaner;
32 import com.android.tradefed.targetprep.TargetSetupError;
33 import com.android.tradefed.testtype.IInvocationContextReceiver;
34 import com.android.tradefed.testtype.suite.TestSuiteInfo;
35 import com.android.tradefed.util.FileUtil;
36 import com.android.tradefed.util.StreamUtil;
37 
38 import org.json.JSONException;
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.net.URL;
46 import java.util.List;
47 
48 /** Pushes dynamic config files from config repository */
49 @OptionClass(alias = "dynamic-config-pusher")
50 public class DynamicConfigPusher extends BaseTargetPreparer
51         implements ITargetCleaner, IInvocationContextReceiver {
52     public enum TestTarget {
53         DEVICE,
54         HOST
55     }
56 
57     /* API Key for compatibility test project, used for dynamic configuration. */
58     private static final String API_KEY = "AIzaSyAbwX5JRlmsLeygY2WWihpIJPXFLueOQ3U";
59 
60     @Option(name = "api-key", description = "API key for for dynamic configuration.")
61     private String mApiKey = API_KEY;
62 
63     @Option(name = "cleanup", description = "Whether to remove config files from the test " +
64             "target after test completion.")
65     private boolean mCleanup = true;
66 
67     @Option(name = "config-url", description = "The url path of the dynamic config. If set, " +
68             "will override the default config location defined in CompatibilityBuildProvider.")
69     private String mConfigUrl = "https://androidpartner.googleapis.com/v1/dynamicconfig/" +
70             "suites/{suite-name}/modules/{module}/version/{version}?key={api-key}";
71 
72     @Option(name="config-filename", description = "The module name for module-level " +
73             "configurations, or the suite name for suite-level configurations")
74     private String mModuleName = null;
75 
76     @Option(name = "target", description = "The test target, \"device\" or \"host\"",
77             mandatory = true)
78     private TestTarget mTarget;
79 
80     @Option(name = "version", description = "The version of the configuration to retrieve " +
81             "from the server, e.g. \"1.0\". Defaults to suite version string.")
82     private String mVersion;
83 
84     // Options for getting the dynamic file from resources.
85     @Option(name = "extract-from-resource",
86             description = "Whether to look for the local dynamic config inside the jar resources "
87                 + "or on the local disk.")
88     private boolean mExtractFromResource = false;
89 
90     @Option(name = "dynamic-resource-name",
91             description = "When using --extract-from-resource, this option allow to specify the "
92                 + "resource name, instead of the module name for the lookup. File will still be "
93                 + "logged under the module name.")
94     private String mResourceFileName = null;
95 
96     @Option(name = "dynamic-config-name",
97             description = "The dynamic config name for module-level configurations, or the "
98                 + "suite name for suite-level configurations.")
99     private String mDynamicConfigName = null;
100 
101     private String mDeviceFilePushed;
102 
103     private IInvocationContext mModuleContext = null;
104 
setModuleName(String moduleName)105     void setModuleName(String moduleName) {
106         mModuleName = moduleName;
107     }
108 
109     /** {@inheritDoc} */
110     @Override
setInvocationContext(IInvocationContext invocationContext)111     public void setInvocationContext(IInvocationContext invocationContext) {
112         mModuleContext = invocationContext;
113     }
114 
115     /**
116      * {@inheritDoc}
117      */
118     @Override
setUp(ITestDevice device, IBuildInfo buildInfo)119     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
120             DeviceNotAvailableException {
121 
122         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
123 
124         File localConfigFile = getLocalConfigFile(buildHelper, device);
125 
126         String suiteName =
127                 (mModuleContext != null) ? getSuiteName() : TestSuiteInfo.getInstance().getName();
128         // Ensure mModuleName is set.
129         if (mModuleName == null) {
130             mModuleName = suiteName.toLowerCase();
131             CLog.w("Option config-filename isn't set. Using suite-name '%s'", mModuleName);
132             if (buildHelper.getDynamicConfigFiles().get(mModuleName) != null) {
133                 CLog.i("Dynamic config file already collected, skipping DynamicConfigPusher.");
134                 return;
135             }
136         }
137         if (mVersion == null) {
138             mVersion = buildHelper.getSuiteVersion();
139         }
140 
141         String apfeConfigInJson = null;
142         String requestUrl = null;
143         try {
144             requestUrl = mConfigUrl.replace("{suite-name}", suiteName)
145                     .replace("{module}", mModuleName)
146                     .replace("{version}", mVersion)
147                     .replace("{api-key}", mApiKey);
148             java.net.URL request = new URL(requestUrl);
149             apfeConfigInJson = StreamUtil.getStringFromStream(request.openStream());
150         } catch (IOException e) {
151             CLog.w(e);
152         }
153 
154         // Use DynamicConfigHandler to merge local and service configuration into one file
155         File hostFile = mergeConfigFiles(localConfigFile, apfeConfigInJson, mModuleName, device);
156 
157         if (TestTarget.DEVICE.equals(mTarget)) {
158             String deviceDest = String.format("%s%s.dynamic",
159                     DynamicConfig.CONFIG_FOLDER_ON_DEVICE, mModuleName);
160             if (!device.pushFile(hostFile, deviceDest)) {
161                 throw new TargetSetupError(String.format(
162                         "Failed to push local '%s' to remote '%s'", hostFile.getAbsolutePath(),
163                         deviceDest), device.getDeviceDescriptor());
164             }
165             mDeviceFilePushed = deviceDest;
166         }
167         // add host file to build
168         buildHelper.addDynamicConfigFile(mModuleName, hostFile);
169     }
170 
171     /**
172      * {@inheritDoc}
173      */
174     @Override
tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)175     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
176             throws DeviceNotAvailableException {
177         // Remove any file we have pushed to the device, host file will be moved to the result
178         // directory by ResultReporter upon invocation completion.
179         if (mDeviceFilePushed != null && !(e instanceof DeviceNotAvailableException) && mCleanup) {
180             device.deleteFile(mDeviceFilePushed);
181         }
182     }
183 
184     /**
185      * Return the the first element of test-suite-tag from configuration if it's not empty,
186      * otherwise, return the name from test-suite-info.properties.
187      */
188     @VisibleForTesting
getSuiteName()189     String getSuiteName() {
190         List<String> testSuiteTags = mModuleContext.getConfigurationDescriptor().getSuiteTags();
191         String suiteName = null;
192         if (!testSuiteTags.isEmpty()) {
193             if (testSuiteTags.size() >= 2) {
194                 CLog.i("More than 2 test-suite-tag are defined. test-suite-tag: " + testSuiteTags);
195             }
196             suiteName = testSuiteTags.get(0).toUpperCase();
197             CLog.i(
198                     "Replacing {suite-name} placeholder with %s from test suite tags in dynamic "
199                             + "config url.",
200                     suiteName);
201         } else {
202             suiteName = TestSuiteInfo.getInstance().getName();
203             CLog.i(
204                     "Replacing {suite-name} placeholder with %s from TestSuiteInfo in dynamic "
205                             + "config url.",
206                     suiteName);
207         }
208         return suiteName;
209     }
210 
211     @VisibleForTesting
getLocalConfigFile(CompatibilityBuildHelper buildHelper, ITestDevice device)212     final File getLocalConfigFile(CompatibilityBuildHelper buildHelper, ITestDevice device)
213             throws TargetSetupError {
214         File localConfigFile = null;
215         if (mExtractFromResource) {
216             String lookupName = (mResourceFileName != null) ? mResourceFileName : mModuleName;
217             InputStream dynamicFileRes = getClass().getResourceAsStream(
218                     String.format("/%s.dynamic", lookupName));
219             try {
220                 localConfigFile = FileUtil.createTempFile(lookupName, ".dynamic");
221                 FileUtil.writeToFile(dynamicFileRes, localConfigFile);
222             } catch (IOException e) {
223                 FileUtil.deleteFile(localConfigFile);
224                 throw new TargetSetupError(
225                         String.format("Fail to unpack '%s.dynamic' from resources", lookupName),
226                         e, device.getDeviceDescriptor());
227             }
228             return localConfigFile;
229         }
230 
231         // If not from resources look at local path.
232         try {
233             String lookupName = (mDynamicConfigName != null) ? mDynamicConfigName : mModuleName;
234             localConfigFile = buildHelper.getTestFile(String.format("%s.dynamic", lookupName));
235         } catch (FileNotFoundException e) {
236             throw new TargetSetupError("Cannot get local dynamic config file from test directory",
237                     e, device.getDeviceDescriptor());
238         }
239         return localConfigFile;
240     }
241 
242     @VisibleForTesting
mergeConfigFiles(File localConfigFile, String apfeConfigInJson, String moduleName, ITestDevice device)243     File mergeConfigFiles(File localConfigFile, String apfeConfigInJson, String moduleName,
244             ITestDevice device) throws TargetSetupError {
245         File hostFile = null;
246         try {
247             hostFile = DynamicConfigHandler.getMergedDynamicConfigFile(
248                     localConfigFile, apfeConfigInJson, moduleName);
249             return hostFile;
250         } catch (IOException | XmlPullParserException | JSONException e) {
251             throw new TargetSetupError("Cannot get merged dynamic config file", e,
252                     device.getDeviceDescriptor());
253         } finally {
254             if (mExtractFromResource) {
255                 FileUtil.deleteFile(localConfigFile);
256             }
257         }
258     }
259 }
260