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.ddmlib.Log; 23 import com.android.tradefed.build.IBuildInfo; 24 import com.android.tradefed.config.Option; 25 import com.android.tradefed.config.OptionClass; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.ITestDevice; 28 import com.android.tradefed.log.LogUtil; 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.util.FileUtil; 34 import com.android.tradefed.util.StreamUtil; 35 36 import org.json.JSONException; 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.File; 40 import java.io.FileNotFoundException; 41 import java.io.IOException; 42 import java.io.InputStream; 43 import java.net.URL; 44 45 /** 46 * Pushes dynamic config files from config repository 47 */ 48 @OptionClass(alias="dynamic-config-pusher") 49 public class DynamicConfigPusher extends BaseTargetPreparer implements ITargetCleaner { 50 public enum TestTarget { 51 DEVICE, 52 HOST 53 } 54 55 private static final String LOG_TAG = DynamicConfigPusher.class.getSimpleName(); 56 57 @Option(name = "cleanup", description = "Whether to remove config files from the test " + 58 "target after test completion.") 59 private boolean mCleanup = true; 60 61 @Option(name = "config-url", description = "The url path of the dynamic config. If set, " + 62 "will override the default config location defined in CompatibilityBuildProvider.") 63 private String mConfigUrl; 64 65 @Option(name="config-filename", description = "The module name for module-level " + 66 "configurations, or the suite name for suite-level configurations", mandatory = true) 67 private String mModuleName; 68 69 @Option(name = "target", description = "The test target, \"device\" or \"host\"", 70 mandatory = true) 71 private TestTarget mTarget; 72 73 @Option(name = "version", description = "The version of the configuration to retrieve " + 74 "from the server, e.g. \"1.0\". Defaults to suite version string.") 75 private String mVersion; 76 77 // Options for getting the dynamic file from resources. 78 @Option(name = "extract-from-resource", 79 description = "Whether to look for the local dynamic config inside the jar resources " 80 + "or on the local disk.") 81 private boolean mExtractFromResource = false; 82 83 @Option(name = "dynamic-resource-name", 84 description = "When using --extract-from-resource, this option allow to specify the " 85 + "resource name, instead of the module name for the lookup. File will still be " 86 + "logged under the module name.") 87 private String mResourceFileName = null; 88 89 private String mDeviceFilePushed; 90 91 void setModuleName(String moduleName) { 92 mModuleName = moduleName; 93 } 94 95 /** 96 * {@inheritDoc} 97 */ 98 @Override 99 public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError, 100 DeviceNotAvailableException { 101 102 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo); 103 104 File localConfigFile = getLocalConfigFile(buildHelper, device); 105 106 if (mVersion == null) { 107 mVersion = buildHelper.getSuiteVersion(); 108 } 109 110 String apfeConfigInJson = null; 111 String originUrl = (mConfigUrl != null) ? mConfigUrl : buildHelper.getDynamicConfigUrl(); 112 113 if (originUrl != null) { 114 String requestUrl = originUrl; 115 try { 116 requestUrl = originUrl 117 .replace("{module}", mModuleName).replace("{version}", mVersion); 118 java.net.URL request = new URL(requestUrl); 119 apfeConfigInJson = StreamUtil.getStringFromStream(request.openStream()); 120 } catch (IOException e) { 121 LogUtil.printLog(Log.LogLevel.WARN, LOG_TAG, 122 "Cannot download and parse json config from URL " + requestUrl); 123 } 124 } else { 125 LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG, 126 "Dynamic config override URL is not set, using local configuration values"); 127 } 128 129 // Use DynamicConfigHandler to merge local and service configuration into one file 130 File hostFile = mergeConfigFiles(localConfigFile, apfeConfigInJson, mModuleName, device); 131 132 if (TestTarget.DEVICE.equals(mTarget)) { 133 String deviceDest = String.format("%s%s.dynamic", 134 DynamicConfig.CONFIG_FOLDER_ON_DEVICE, mModuleName); 135 if (!device.pushFile(hostFile, deviceDest)) { 136 throw new TargetSetupError(String.format( 137 "Failed to push local '%s' to remote '%s'", hostFile.getAbsolutePath(), 138 deviceDest), device.getDeviceDescriptor()); 139 } 140 mDeviceFilePushed = deviceDest; 141 } 142 // add host file to build 143 buildHelper.addDynamicConfigFile(mModuleName, hostFile); 144 } 145 146 /** 147 * {@inheritDoc} 148 */ 149 @Override 150 public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) 151 throws DeviceNotAvailableException { 152 // Remove any file we have pushed to the device, host file will be moved to the result 153 // directory by ResultReporter upon invocation completion. 154 if (mDeviceFilePushed != null && !(e instanceof DeviceNotAvailableException) && mCleanup) { 155 device.executeShellCommand("rm -r " + mDeviceFilePushed); 156 } 157 } 158 159 @VisibleForTesting 160 final File getLocalConfigFile(CompatibilityBuildHelper buildHelper, ITestDevice device) 161 throws TargetSetupError { 162 File localConfigFile = null; 163 if (mExtractFromResource) { 164 String lookupName = (mResourceFileName != null) ? mResourceFileName : mModuleName; 165 InputStream dynamicFileRes = getClass().getResourceAsStream( 166 String.format("/%s.dynamic", lookupName)); 167 try { 168 localConfigFile = FileUtil.createTempFile(lookupName, ".dynamic"); 169 FileUtil.writeToFile(dynamicFileRes, localConfigFile); 170 } catch (IOException e) { 171 FileUtil.deleteFile(localConfigFile); 172 throw new TargetSetupError( 173 String.format("Fail to unpack '%s.dynamic' from resources", lookupName), 174 e, device.getDeviceDescriptor()); 175 } 176 return localConfigFile; 177 } 178 179 // If not from resources look at local path. 180 try { 181 localConfigFile = buildHelper.getTestFile(String.format("%s.dynamic", mModuleName)); 182 } catch (FileNotFoundException e) { 183 throw new TargetSetupError("Cannot get local dynamic config file from test directory", 184 e, device.getDeviceDescriptor()); 185 } 186 return localConfigFile; 187 } 188 189 @VisibleForTesting 190 File mergeConfigFiles(File localConfigFile, String apfeConfigInJson, String moduleName, 191 ITestDevice device) throws TargetSetupError { 192 File hostFile = null; 193 try { 194 hostFile = DynamicConfigHandler.getMergedDynamicConfigFile( 195 localConfigFile, apfeConfigInJson, moduleName); 196 return hostFile; 197 } catch (IOException | XmlPullParserException | JSONException e) { 198 throw new TargetSetupError("Cannot get merged dynamic config file", e, 199 device.getDeviceDescriptor()); 200 } finally { 201 if (mExtractFromResource) { 202 FileUtil.deleteFile(localConfigFile); 203 } 204 } 205 } 206 } 207