blob: d004fb4fb0f3f308072c84a9d64d9133d2702272 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.compatibility.common.tradefed.targetprep;
import com.android.annotations.VisibleForTesting;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.util.DynamicConfig;
import com.android.compatibility.common.util.DynamicConfigHandler;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.targetprep.BaseTargetPreparer;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.testtype.IInvocationContextReceiver;
import com.android.tradefed.testtype.suite.TestSuiteInfo;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
import org.json.JSONException;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
/** Pushes dynamic config files from config repository */
@OptionClass(alias = "dynamic-config-pusher")
public class DynamicConfigPusher extends BaseTargetPreparer
implements ITargetCleaner, IInvocationContextReceiver {
public enum TestTarget {
DEVICE,
HOST
}
/* API Key for compatibility test project, used for dynamic configuration. */
private static final String API_KEY = "AIzaSyAbwX5JRlmsLeygY2WWihpIJPXFLueOQ3U";
@Option(name = "api-key", description = "API key for for dynamic configuration.")
private String mApiKey = API_KEY;
@Option(name = "cleanup", description = "Whether to remove config files from the test " +
"target after test completion.")
private boolean mCleanup = true;
@Option(name = "config-url", description = "The url path of the dynamic config. If set, " +
"will override the default config location defined in CompatibilityBuildProvider.")
private String mConfigUrl = "https://androidpartner.googleapis.com/v1/dynamicconfig/" +
"suites/{suite-name}/modules/{module}/version/{version}?key={api-key}";
@Option(name="config-filename", description = "The module name for module-level " +
"configurations, or the suite name for suite-level configurations")
private String mModuleName = null;
@Option(name = "target", description = "The test target, \"device\" or \"host\"",
mandatory = true)
private TestTarget mTarget;
@Option(name = "version", description = "The version of the configuration to retrieve " +
"from the server, e.g. \"1.0\". Defaults to suite version string.")
private String mVersion;
// Options for getting the dynamic file from resources.
@Option(name = "extract-from-resource",
description = "Whether to look for the local dynamic config inside the jar resources "
+ "or on the local disk.")
private boolean mExtractFromResource = false;
@Option(name = "dynamic-resource-name",
description = "When using --extract-from-resource, this option allow to specify the "
+ "resource name, instead of the module name for the lookup. File will still be "
+ "logged under the module name.")
private String mResourceFileName = null;
@Option(name = "dynamic-config-name",
description = "The dynamic config name for module-level configurations, or the "
+ "suite name for suite-level configurations.")
private String mDynamicConfigName = null;
private String mDeviceFilePushed;
private IInvocationContext mModuleContext = null;
void setModuleName(String moduleName) {
mModuleName = moduleName;
}
/** {@inheritDoc} */
@Override
public void setInvocationContext(IInvocationContext invocationContext) {
mModuleContext = invocationContext;
}
/**
* {@inheritDoc}
*/
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
DeviceNotAvailableException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
File localConfigFile = getLocalConfigFile(buildHelper, device);
String suiteName =
(mModuleContext != null) ? getSuiteName() : TestSuiteInfo.getInstance().getName();
// Ensure mModuleName is set.
if (mModuleName == null) {
mModuleName = suiteName.toLowerCase();
CLog.w("Option config-filename isn't set. Using suite-name '%s'", mModuleName);
if (buildHelper.getDynamicConfigFiles().get(mModuleName) != null) {
CLog.i("Dynamic config file already collected, skipping DynamicConfigPusher.");
return;
}
}
if (mVersion == null) {
mVersion = buildHelper.getSuiteVersion();
}
String apfeConfigInJson = null;
String requestUrl = null;
try {
requestUrl = mConfigUrl.replace("{suite-name}", suiteName)
.replace("{module}", mModuleName)
.replace("{version}", mVersion)
.replace("{api-key}", mApiKey);
java.net.URL request = new URL(requestUrl);
apfeConfigInJson = StreamUtil.getStringFromStream(request.openStream());
} catch (IOException e) {
CLog.w(e);
}
// Use DynamicConfigHandler to merge local and service configuration into one file
File hostFile = mergeConfigFiles(localConfigFile, apfeConfigInJson, mModuleName, device);
if (TestTarget.DEVICE.equals(mTarget)) {
String deviceDest = String.format("%s%s.dynamic",
DynamicConfig.CONFIG_FOLDER_ON_DEVICE, mModuleName);
if (!device.pushFile(hostFile, deviceDest)) {
throw new TargetSetupError(String.format(
"Failed to push local '%s' to remote '%s'", hostFile.getAbsolutePath(),
deviceDest), device.getDeviceDescriptor());
}
mDeviceFilePushed = deviceDest;
}
// add host file to build
buildHelper.addDynamicConfigFile(mModuleName, hostFile);
}
/**
* {@inheritDoc}
*/
@Override
public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
throws DeviceNotAvailableException {
// Remove any file we have pushed to the device, host file will be moved to the result
// directory by ResultReporter upon invocation completion.
if (mDeviceFilePushed != null && !(e instanceof DeviceNotAvailableException) && mCleanup) {
device.deleteFile(mDeviceFilePushed);
}
}
/**
* Return the the first element of test-suite-tag from configuration if it's not empty,
* otherwise, return the name from test-suite-info.properties.
*/
@VisibleForTesting
String getSuiteName() {
List<String> testSuiteTags = mModuleContext.getConfigurationDescriptor().getSuiteTags();
String suiteName = null;
if (!testSuiteTags.isEmpty()) {
if (testSuiteTags.size() >= 2) {
CLog.i("More than 2 test-suite-tag are defined. test-suite-tag: " + testSuiteTags);
}
suiteName = testSuiteTags.get(0).toUpperCase();
CLog.i(
"Replacing {suite-name} placeholder with %s from test suite tags in dynamic "
+ "config url.",
suiteName);
} else {
suiteName = TestSuiteInfo.getInstance().getName();
CLog.i(
"Replacing {suite-name} placeholder with %s from TestSuiteInfo in dynamic "
+ "config url.",
suiteName);
}
return suiteName;
}
@VisibleForTesting
final File getLocalConfigFile(CompatibilityBuildHelper buildHelper, ITestDevice device)
throws TargetSetupError {
File localConfigFile = null;
if (mExtractFromResource) {
String lookupName = (mResourceFileName != null) ? mResourceFileName : mModuleName;
InputStream dynamicFileRes = getClass().getResourceAsStream(
String.format("/%s.dynamic", lookupName));
try {
localConfigFile = FileUtil.createTempFile(lookupName, ".dynamic");
FileUtil.writeToFile(dynamicFileRes, localConfigFile);
} catch (IOException e) {
FileUtil.deleteFile(localConfigFile);
throw new TargetSetupError(
String.format("Fail to unpack '%s.dynamic' from resources", lookupName),
e, device.getDeviceDescriptor());
}
return localConfigFile;
}
// If not from resources look at local path.
try {
String lookupName = (mDynamicConfigName != null) ? mDynamicConfigName : mModuleName;
localConfigFile = buildHelper.getTestFile(String.format("%s.dynamic", lookupName));
} catch (FileNotFoundException e) {
throw new TargetSetupError("Cannot get local dynamic config file from test directory",
e, device.getDeviceDescriptor());
}
return localConfigFile;
}
@VisibleForTesting
File mergeConfigFiles(File localConfigFile, String apfeConfigInJson, String moduleName,
ITestDevice device) throws TargetSetupError {
File hostFile = null;
try {
hostFile = DynamicConfigHandler.getMergedDynamicConfigFile(
localConfigFile, apfeConfigInJson, moduleName);
return hostFile;
} catch (IOException | XmlPullParserException | JSONException e) {
throw new TargetSetupError("Cannot get merged dynamic config file", e,
device.getDeviceDescriptor());
} finally {
if (mExtractFromResource) {
FileUtil.deleteFile(localConfigFile);
}
}
}
}