blob: 5ddf18d10c97fa173a0977a6623adcc11152acb8 [file] [log] [blame]
/*
* Copyright (c) 2016 Google Inc. All Rights Reserved.
*
* 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.vts.api;
import com.android.vts.entity.ApiCoverageEntity;
import com.android.vts.entity.BranchEntity;
import com.android.vts.entity.BuildTargetEntity;
import com.android.vts.entity.CodeCoverageEntity;
import com.android.vts.entity.CoverageEntity;
import com.android.vts.entity.DashboardEntity;
import com.android.vts.entity.DeviceInfoEntity;
import com.android.vts.entity.HalApiEntity;
import com.android.vts.entity.ProfilingPointRunEntity;
import com.android.vts.entity.TestCaseRunEntity;
import com.android.vts.entity.TestEntity;
import com.android.vts.entity.TestPlanEntity;
import com.android.vts.entity.TestPlanRunEntity;
import com.android.vts.entity.TestRunEntity;
import com.android.vts.proto.VtsReportMessage;
import com.android.vts.proto.VtsReportMessage.DashboardPostMessage;
import com.android.vts.proto.VtsReportMessage.TestPlanReportMessage;
import com.android.vts.proto.VtsReportMessage.TestReportMessage;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Tokeninfo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.datastore.Key;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import static com.googlecode.objectify.ObjectifyService.ofy;
@Slf4j
/** REST endpoint for posting data to the Dashboard. */
public class DatastoreRestServlet extends BaseApiServlet {
private static String SERVICE_CLIENT_ID;
private static final String SERVICE_NAME = "VTS Dashboard";
@Override
public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
SERVICE_CLIENT_ID = this.systemConfigProp.getProperty("appengine.serviceClientID");
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// Retrieve the params
DashboardPostMessage postMessage;
try {
String payload = request.getReader().lines().collect(Collectors.joining());
byte[] value = Base64.decodeBase64(payload);
postMessage = DashboardPostMessage.parseFrom(value);
} catch (IOException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
log.error("Invalid proto: " + e.getLocalizedMessage());
return;
}
// Verify service account access token.
if (postMessage.hasAccessToken()) {
String accessToken = postMessage.getAccessToken();
log.debug("accessToken => " + accessToken);
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Oauth2 oauth2 =
new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName(SERVICE_NAME)
.build();
Tokeninfo tokenInfo = oauth2.tokeninfo().setAccessToken(accessToken).execute();
if (tokenInfo.getIssuedTo().equals(SERVICE_CLIENT_ID)) {
for (TestReportMessage testReportMessage : postMessage.getTestReportList()) {
this.insertTestReport(testReportMessage);
}
for (TestPlanReportMessage planReportMessage :
postMessage.getTestPlanReportList()) {
this.insertTestPlanReport(planReportMessage);
}
response.setStatus(HttpServletResponse.SC_OK);
} else {
log.warn("service_client_id didn't match!");
log.debug("SERVICE_CLIENT_ID => " + tokenInfo.getIssuedTo());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
} else {
log.error("postMessage do not contain any accessToken!");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
/**
* Upload data from a test report message
*
* @param report The test report containing data to upload.
*/
private void insertTestReport(TestReportMessage report) {
if (!report.hasStartTimestamp()
|| !report.hasEndTimestamp()
|| !report.hasTest()
|| !report.hasHostInfo()
|| !report.hasBuildInfo()) {
// missing information
log.error("Missing information in report !");
return;
}
List<TestEntity> testEntityList = new ArrayList<>();
List<TestRunEntity> testRunEntityList = new ArrayList<>();
List<BranchEntity> branchEntityList = new ArrayList<>();
List<BuildTargetEntity> buildTargetEntityList = new ArrayList<>();
List<CoverageEntity> coverageEntityList = new ArrayList<>();
List<CodeCoverageEntity> codeCoverageEntityList = new ArrayList<>();
List<DeviceInfoEntity> deviceInfoEntityList = new ArrayList<>();
List<ProfilingPointRunEntity> profilingPointRunEntityList = new ArrayList<>();
List<TestCaseRunEntity> testCaseRunEntityList = new ArrayList<>();
List<ApiCoverageEntity> apiCoverageEntityList = new ArrayList<>();
List<?> allEntityList =
Arrays.asList(
testEntityList,
branchEntityList,
buildTargetEntityList,
coverageEntityList,
codeCoverageEntityList,
deviceInfoEntityList,
profilingPointRunEntityList,
testCaseRunEntityList,
apiCoverageEntityList,
testRunEntityList);
long passCount = 0;
long failCount = 0;
long coveredLineCount = 0;
long totalLineCount = 0;
Set<Key> buildTargetKeys = new HashSet<>();
Set<Key> branchKeys = new HashSet<>();
List<Key> profilingPointKeyList = new ArrayList<>();
List<String> linkList = new ArrayList<>();
long startTimestamp = report.getStartTimestamp();
long endTimestamp = report.getEndTimestamp();
String testName = report.getTest().toStringUtf8();
String testBuildId = report.getBuildInfo().getId().toStringUtf8();
String hostName = report.getHostInfo().getHostname().toStringUtf8();
TestEntity testEntity = new TestEntity(testName);
com.googlecode.objectify.Key testRunKey =
testEntity.getTestRunKey(report.getStartTimestamp());
testEntityList.add(testEntity);
int testCaseRunEntityIndex = 0;
testCaseRunEntityList.add(new TestCaseRunEntity());
// Process test cases
for (VtsReportMessage.TestCaseReportMessage testCase : report.getTestCaseList()) {
String testCaseName = testCase.getName().toStringUtf8();
VtsReportMessage.TestCaseResult result = testCase.getTestResult();
// Track global pass/fail counts
if (result == VtsReportMessage.TestCaseResult.TEST_CASE_RESULT_PASS) {
++passCount;
} else if (result != VtsReportMessage.TestCaseResult.TEST_CASE_RESULT_SKIP) {
++failCount;
}
if (testCase.getSystraceCount() > 0
&& testCase.getSystraceList().get(0).getUrlCount() > 0) {
String systraceLink = testCase.getSystraceList().get(0).getUrl(0).toStringUtf8();
linkList.add(systraceLink);
}
// Process coverage data for test case
for (VtsReportMessage.CoverageReportMessage coverage : testCase.getCoverageList()) {
CoverageEntity coverageEntity =
CoverageEntity.fromCoverageReport(testRunKey, testCaseName, coverage);
if (coverageEntity == null) {
log.warn("Invalid coverage report in test run " + testRunKey);
} else {
coveredLineCount += coverageEntity.getCoveredCount();
totalLineCount += coverageEntity.getTotalCount();
coverageEntityList.add(coverageEntity);
}
}
// Process profiling data for test case
for (VtsReportMessage.ProfilingReportMessage profiling : testCase.getProfilingList()) {
ProfilingPointRunEntity profilingPointRunEntity =
ProfilingPointRunEntity.fromProfilingReport(testRunKey, profiling);
if (profilingPointRunEntity == null) {
log.warn("Invalid profiling report in test run " + testRunKey);
} else {
profilingPointRunEntityList.add(profilingPointRunEntity);
profilingPointKeyList.add(profilingPointRunEntity.getKey());
testEntity.setHasProfilingData(true);
}
}
TestCaseRunEntity testCaseRunEntity = testCaseRunEntityList.get(testCaseRunEntityIndex);
if (!testCaseRunEntity.addTestCase(testCaseName, result.getNumber())) {
testCaseRunEntity = new TestCaseRunEntity();
testCaseRunEntity.addTestCase(testCaseName, result.getNumber());
testCaseRunEntityList.add(testCaseRunEntity);
testCaseRunEntityIndex++;
}
}
// Process device information
long testRunType = 0;
for (VtsReportMessage.AndroidDeviceInfoMessage device : report.getDeviceInfoList()) {
DeviceInfoEntity deviceInfoEntity =
DeviceInfoEntity.fromDeviceInfoMessage(testRunKey, device);
if (deviceInfoEntity == null) {
log.warn("Invalid device info in test run " + testRunKey);
} else {
// Run type on devices must be the same, else set to OTHER
TestRunEntity.TestRunType runType =
TestRunEntity.TestRunType.fromBuildId(deviceInfoEntity.getBuildId());
if (runType == null) {
testRunType = TestRunEntity.TestRunType.OTHER.getNumber();
} else {
testRunType = runType.getNumber();
}
deviceInfoEntityList.add(deviceInfoEntity);
BuildTargetEntity target = new BuildTargetEntity(deviceInfoEntity.getBuildFlavor());
if (buildTargetKeys.add(target.getKey())) {
buildTargetEntityList.add(target);
}
BranchEntity branch = new BranchEntity(deviceInfoEntity.getBranch());
if (branchKeys.add(branch.getKey())) {
branchEntityList.add(branch);
}
}
}
// Overall run type should be determined by the device builds unless test build is OTHER
if (testRunType == TestRunEntity.TestRunType.OTHER.getNumber()) {
testRunType = TestRunEntity.TestRunType.fromBuildId(testBuildId).getNumber();
} else if (TestRunEntity.TestRunType.fromBuildId(testBuildId)
== TestRunEntity.TestRunType.OTHER) {
testRunType = TestRunEntity.TestRunType.OTHER.getNumber();
}
// Process global coverage data
for (VtsReportMessage.CoverageReportMessage coverage : report.getCoverageList()) {
CoverageEntity coverageEntity =
CoverageEntity.fromCoverageReport(testRunKey, new String(), coverage);
if (coverageEntity == null) {
log.warn("Invalid coverage report in test run " + testRunKey);
} else {
coveredLineCount += coverageEntity.getCoveredCount();
totalLineCount += coverageEntity.getTotalCount();
coverageEntityList.add(coverageEntity);
}
}
// Process global API coverage data
for (VtsReportMessage.ApiCoverageReportMessage apiCoverage : report.getApiCoverageList()) {
VtsReportMessage.HalInterfaceMessage halInterfaceMessage =
apiCoverage.getHalInterface();
List<String> halApiList =
apiCoverage
.getHalApiList()
.stream()
.map(h -> h.toStringUtf8())
.collect(Collectors.toList());
List<String> coveredHalApiList =
apiCoverage
.getCoveredHalApiList()
.stream()
.map(h -> h.toStringUtf8())
.collect(Collectors.toList());
ApiCoverageEntity apiCoverageEntity =
new ApiCoverageEntity(
testRunKey,
halInterfaceMessage.getHalPackageName().toStringUtf8(),
halInterfaceMessage.getHalVersionMajor(),
halInterfaceMessage.getHalVersionMinor(),
halInterfaceMessage.getHalInterfaceName().toStringUtf8(),
halApiList,
coveredHalApiList);
apiCoverageEntityList.add(apiCoverageEntity);
}
// Process global profiling data
for (VtsReportMessage.ProfilingReportMessage profiling : report.getProfilingList()) {
ProfilingPointRunEntity profilingPointRunEntity =
ProfilingPointRunEntity.fromProfilingReport(testRunKey, profiling);
if (profilingPointRunEntity == null) {
log.warn("Invalid profiling report in test run " + testRunKey);
} else {
profilingPointRunEntityList.add(profilingPointRunEntity);
profilingPointKeyList.add(profilingPointRunEntity.getKey());
testEntity.setHasProfilingData(true);
}
}
// Process log data
for (VtsReportMessage.LogMessage log : report.getLogList()) {
if (log.hasUrl()) {
linkList.add(log.getUrl().toStringUtf8());
}
}
// Process url resource
for (VtsReportMessage.UrlResourceMessage resource : report.getLinkResourceList()) {
if (resource.hasUrl()) {
linkList.add(resource.getUrl().toStringUtf8());
}
}
boolean hasCodeCoverage = totalLineCount > 0 && coveredLineCount >= 0;
TestRunEntity testRunEntity =
new TestRunEntity(
testEntity.getOldKey(),
testRunType,
startTimestamp,
endTimestamp,
testBuildId,
hostName,
passCount,
failCount,
hasCodeCoverage,
new ArrayList<>(),
linkList);
testRunEntityList.add(testRunEntity);
CodeCoverageEntity codeCoverageEntity =
new CodeCoverageEntity(
testRunEntity.getId(),
testRunEntity.getKey(),
coveredLineCount,
totalLineCount);
codeCoverageEntityList.add(codeCoverageEntity);
ofy().transact(
() -> {
List<Long> testCaseIds = new ArrayList<>();
for (Object entity : allEntityList) {
if (entity instanceof List) {
List listEntity = (List) entity;
if (listEntity.size() > 0
&& listEntity.get(0) instanceof TestCaseRunEntity) {
List<TestCaseRunEntity> dashboardEntityList =
(List<TestCaseRunEntity>) entity;
Map<
com.googlecode.objectify.Key<
TestCaseRunEntity>,
TestCaseRunEntity>
testCaseRunEntityMap =
DashboardEntity.saveAll(
dashboardEntityList,
this
.MAX_ENTITY_SIZE_PER_TRANSACTION);
testCaseIds =
testCaseRunEntityMap
.values()
.stream()
.map(
testCaseRunEntity ->
testCaseRunEntity.getId())
.collect(Collectors.toList());
} else if (listEntity.size() > 0
&& listEntity.get(0) instanceof TestRunEntity) {
List<TestRunEntity> dashboardEntityList =
(List<TestRunEntity>) entity;
dashboardEntityList.get(0).setTestCaseIds(testCaseIds);
DashboardEntity.saveAll(
dashboardEntityList,
this.MAX_ENTITY_SIZE_PER_TRANSACTION);
} else {
List<DashboardEntity> dashboardEntityList =
(List<DashboardEntity>) entity;
DashboardEntity.saveAll(
dashboardEntityList,
this.MAX_ENTITY_SIZE_PER_TRANSACTION);
}
}
}
});
}
/**
* Upload data from a test plan report message
*
* @param report The test plan report containing data to upload.
*/
private void insertTestPlanReport(TestPlanReportMessage report) {
List<DeviceInfoEntity> deviceInfoEntityList = new ArrayList<>();
List<HalApiEntity> halApiEntityList = new ArrayList<>();
List allEntityList = Arrays.asList(deviceInfoEntityList, halApiEntityList);
List<String> testModules = report.getTestModuleNameList();
List<Long> testTimes = report.getTestModuleStartTimestampList();
if (testModules.size() != testTimes.size() || !report.hasTestPlanName()) {
log.error("TestPlanReportMessage is missing information.");
return;
}
String testPlanName = report.getTestPlanName();
TestPlanEntity testPlanEntity = new TestPlanEntity(testPlanName);
List<com.googlecode.objectify.Key<TestRunEntity>> testRunKeyList = new ArrayList<>();
for (int index = 0; index < testModules.size(); index++) {
String test = testModules.get(index);
long time = testTimes.get(index);
com.googlecode.objectify.Key testKey =
com.googlecode.objectify.Key.create(TestEntity.class, test);
com.googlecode.objectify.Key testRunKey =
com.googlecode.objectify.Key.create(testKey, TestRunEntity.class, time);
testRunKeyList.add(testRunKey);
}
Map<com.googlecode.objectify.Key<TestRunEntity>, TestRunEntity> testRunEntityMap =
ofy().load().keys(() -> testRunKeyList.iterator());
testRunKeyList.forEach(
(v) -> {
log.debug("TestRunEntity key value => " + v);
});
log.debug("testRunEntityMap value => " + testRunEntityMap.values());
log.debug("testRunEntityMap keySet => " + testRunEntityMap.keySet());
long passCount = 0;
long failCount = 0;
long startTimestamp = -1;
long endTimestamp = -1;
String testBuildId = null;
long testType = -1;
Set<DeviceInfoEntity> deviceInfoEntitySet = new HashSet<>();
for (TestRunEntity testRunEntity : testRunEntityMap.values()) {
passCount += testRunEntity.getPassCount();
failCount += testRunEntity.getFailCount();
if (startTimestamp < 0 || testRunEntity.getStartTimestamp() < startTimestamp) {
startTimestamp = testRunEntity.getStartTimestamp();
}
if (endTimestamp < 0 || testRunEntity.getEndTimestamp() > endTimestamp) {
endTimestamp = testRunEntity.getEndTimestamp();
}
testType = testRunEntity.getType();
testBuildId = testRunEntity.getTestBuildId();
List<DeviceInfoEntity> deviceInfoEntityListWithTestRunKey =
ofy().load()
.type(DeviceInfoEntity.class)
.ancestor(testRunEntity.getOfyKey())
.list();
for (DeviceInfoEntity deviceInfoEntity : deviceInfoEntityListWithTestRunKey) {
deviceInfoEntitySet.add(deviceInfoEntity);
}
}
if (startTimestamp < 0 || testBuildId == null || testType == -1) {
log.debug("startTimestamp => " + startTimestamp);
log.debug("testBuildId => " + testBuildId);
log.debug("type => " + testType);
log.error("Couldn't infer test run information from runs.");
return;
}
TestPlanRunEntity testPlanRunEntity =
new TestPlanRunEntity(
testPlanEntity.getKey(),
testPlanName,
testType,
startTimestamp,
endTimestamp,
testBuildId,
passCount,
failCount,
0L,
0L,
testRunKeyList);
// Create the device infos.
for (DeviceInfoEntity device : deviceInfoEntitySet) {
deviceInfoEntityList.add(device.copyWithParent(testPlanRunEntity.getOfyKey()));
}
// Process global HAL API coverage data
for (VtsReportMessage.ApiCoverageReportMessage apiCoverage : report.getHalApiReportList()) {
VtsReportMessage.HalInterfaceMessage halInterfaceMessage =
apiCoverage.getHalInterface();
List<String> halApiList =
apiCoverage
.getHalApiList()
.stream()
.map(h -> h.toStringUtf8())
.collect(Collectors.toList());
List<String> coveredHalApiList =
apiCoverage
.getCoveredHalApiList()
.stream()
.map(h -> h.toStringUtf8())
.collect(Collectors.toList());
HalApiEntity halApiEntity =
new HalApiEntity(
testPlanRunEntity.getOfyKey(),
halInterfaceMessage.getHalReleaseLevel().toStringUtf8(),
halInterfaceMessage.getHalPackageName().toStringUtf8(),
halInterfaceMessage.getHalVersionMajor(),
halInterfaceMessage.getHalVersionMinor(),
halInterfaceMessage.getHalInterfaceName().toStringUtf8(),
halApiList,
coveredHalApiList);
halApiEntityList.add(halApiEntity);
}
ofy().transact(
() -> {
testPlanEntity.save();
testPlanRunEntity.save();
for (Object entity : allEntityList) {
List<DashboardEntity> dashboardEntityList =
(List<DashboardEntity>) entity;
Map<com.googlecode.objectify.Key<DashboardEntity>, DashboardEntity>
mapInfo =
DashboardEntity.saveAll(
dashboardEntityList,
this.MAX_ENTITY_SIZE_PER_TRANSACTION);
}
});
// Add the task to calculate total number API list.
testPlanRunEntity.addCoverageApiTask();
}
}