1 /* 2 * Copyright (c) 2016 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you 5 * may not use this file except in compliance with the License. You may 6 * 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 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 17 package com.android.vts.api; 18 19 import com.android.vts.entity.ApiCoverageEntity; 20 import com.android.vts.entity.BranchEntity; 21 import com.android.vts.entity.BuildTargetEntity; 22 import com.android.vts.entity.CodeCoverageEntity; 23 import com.android.vts.entity.CoverageEntity; 24 import com.android.vts.entity.DashboardEntity; 25 import com.android.vts.entity.DeviceInfoEntity; 26 import com.android.vts.entity.HalApiEntity; 27 import com.android.vts.entity.ProfilingPointRunEntity; 28 import com.android.vts.entity.TestCaseRunEntity; 29 import com.android.vts.entity.TestEntity; 30 import com.android.vts.entity.TestPlanEntity; 31 import com.android.vts.entity.TestPlanRunEntity; 32 import com.android.vts.entity.TestRunEntity; 33 import com.android.vts.proto.VtsReportMessage; 34 import com.android.vts.proto.VtsReportMessage.DashboardPostMessage; 35 import com.android.vts.proto.VtsReportMessage.TestPlanReportMessage; 36 import com.android.vts.proto.VtsReportMessage.TestReportMessage; 37 import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 38 import com.google.api.client.http.javanet.NetHttpTransport; 39 import com.google.api.client.json.jackson.JacksonFactory; 40 import com.google.api.services.oauth2.Oauth2; 41 import com.google.api.services.oauth2.model.Tokeninfo; 42 43 import java.io.IOException; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.HashSet; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.Set; 50 import java.util.stream.Collectors; 51 import javax.servlet.ServletConfig; 52 import javax.servlet.ServletException; 53 import javax.servlet.http.HttpServletRequest; 54 import javax.servlet.http.HttpServletResponse; 55 56 import com.google.appengine.api.datastore.Key; 57 import lombok.extern.slf4j.Slf4j; 58 import org.apache.commons.codec.binary.Base64; 59 60 import static com.googlecode.objectify.ObjectifyService.ofy; 61 62 @Slf4j 63 /** REST endpoint for posting data to the Dashboard. */ 64 public class DatastoreRestServlet extends BaseApiServlet { 65 private static String SERVICE_CLIENT_ID; 66 private static final String SERVICE_NAME = "VTS Dashboard"; 67 68 @Override init(ServletConfig cfg)69 public void init(ServletConfig cfg) throws ServletException { 70 super.init(cfg); 71 72 SERVICE_CLIENT_ID = this.systemConfigProp.getProperty("appengine.serviceClientID"); 73 } 74 75 @Override doPost(HttpServletRequest request, HttpServletResponse response)76 public void doPost(HttpServletRequest request, HttpServletResponse response) 77 throws IOException { 78 // Retrieve the params 79 DashboardPostMessage postMessage; 80 try { 81 String payload = request.getReader().lines().collect(Collectors.joining()); 82 byte[] value = Base64.decodeBase64(payload); 83 postMessage = DashboardPostMessage.parseFrom(value); 84 } catch (IOException e) { 85 response.setStatus(HttpServletResponse.SC_BAD_REQUEST); 86 log.error("Invalid proto: " + e.getLocalizedMessage()); 87 return; 88 } 89 90 String resultMsg = ""; 91 // Verify service account access token. 92 if (postMessage.hasAccessToken()) { 93 String accessToken = postMessage.getAccessToken(); 94 log.debug("accessToken => " + accessToken); 95 96 GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken); 97 Oauth2 oauth2 = 98 new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), credential) 99 .setApplicationName(SERVICE_NAME) 100 .build(); 101 Tokeninfo tokenInfo = oauth2.tokeninfo().setAccessToken(accessToken).execute(); 102 if (tokenInfo.getIssuedTo().equals(SERVICE_CLIENT_ID)) { 103 for (TestReportMessage testReportMessage : postMessage.getTestReportList()) { 104 this.insertTestReport(testReportMessage); 105 } 106 107 for (TestPlanReportMessage planReportMessage : 108 postMessage.getTestPlanReportList()) { 109 this.insertTestPlanReport(planReportMessage); 110 } 111 112 response.setStatus(HttpServletResponse.SC_OK); 113 resultMsg = "Success!!"; 114 } else { 115 log.warn("service_client_id didn't match!"); 116 log.debug("SERVICE_CLIENT_ID => " + tokenInfo.getIssuedTo()); 117 resultMsg = "Your SERVICE_CLIENT_ID is incorrect!"; 118 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 119 } 120 } else { 121 log.error("postMessage do not contain any accessToken!"); 122 resultMsg = "Your message do not have access token!"; 123 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 124 } 125 response.setContentType("application/json"); 126 response.setCharacterEncoding("UTF-8"); 127 response.getWriter().write("{'result_msg': " + resultMsg + "}"); 128 } 129 130 /** 131 * Upload data from a test report message 132 * 133 * @param report The test report containing data to upload. 134 */ insertTestReport(TestReportMessage report)135 private void insertTestReport(TestReportMessage report) { 136 137 if (!report.hasStartTimestamp() 138 || !report.hasEndTimestamp() 139 || !report.hasTest() 140 || !report.hasHostInfo() 141 || !report.hasBuildInfo()) { 142 // missing information 143 log.error("Missing information in report !"); 144 return; 145 } 146 147 List<TestEntity> testEntityList = new ArrayList<>(); 148 List<TestRunEntity> testRunEntityList = new ArrayList<>(); 149 List<BranchEntity> branchEntityList = new ArrayList<>(); 150 List<BuildTargetEntity> buildTargetEntityList = new ArrayList<>(); 151 List<CoverageEntity> coverageEntityList = new ArrayList<>(); 152 List<CodeCoverageEntity> codeCoverageEntityList = new ArrayList<>(); 153 List<DeviceInfoEntity> deviceInfoEntityList = new ArrayList<>(); 154 List<ProfilingPointRunEntity> profilingPointRunEntityList = new ArrayList<>(); 155 List<TestCaseRunEntity> testCaseRunEntityList = new ArrayList<>(); 156 List<ApiCoverageEntity> apiCoverageEntityList = new ArrayList<>(); 157 158 List<?> allEntityList = 159 Arrays.asList( 160 testEntityList, 161 branchEntityList, 162 buildTargetEntityList, 163 coverageEntityList, 164 codeCoverageEntityList, 165 deviceInfoEntityList, 166 profilingPointRunEntityList, 167 testCaseRunEntityList, 168 apiCoverageEntityList, 169 testRunEntityList); 170 171 long passCount = 0; 172 long failCount = 0; 173 long coveredLineCount = 0; 174 long totalLineCount = 0; 175 176 Set<Key> buildTargetKeys = new HashSet<>(); 177 Set<Key> branchKeys = new HashSet<>(); 178 List<Key> profilingPointKeyList = new ArrayList<>(); 179 List<String> linkList = new ArrayList<>(); 180 181 long startTimestamp = report.getStartTimestamp(); 182 long endTimestamp = report.getEndTimestamp(); 183 String testName = report.getTest().toStringUtf8(); 184 String testBuildId = report.getBuildInfo().getId().toStringUtf8(); 185 String hostName = report.getHostInfo().getHostname().toStringUtf8(); 186 187 TestEntity testEntity = new TestEntity(testName); 188 189 com.googlecode.objectify.Key testRunKey = 190 testEntity.getTestRunKey(report.getStartTimestamp()); 191 192 testEntityList.add(testEntity); 193 194 int testCaseRunEntityIndex = 0; 195 testCaseRunEntityList.add(new TestCaseRunEntity()); 196 // Process test cases 197 for (VtsReportMessage.TestCaseReportMessage testCase : report.getTestCaseList()) { 198 String testCaseName = testCase.getName().toStringUtf8(); 199 VtsReportMessage.TestCaseResult result = testCase.getTestResult(); 200 // Track global pass/fail counts 201 if (result == VtsReportMessage.TestCaseResult.TEST_CASE_RESULT_PASS) { 202 ++passCount; 203 } else if (result != VtsReportMessage.TestCaseResult.TEST_CASE_RESULT_SKIP) { 204 ++failCount; 205 } 206 if (testCase.getSystraceCount() > 0 207 && testCase.getSystraceList().get(0).getUrlCount() > 0) { 208 String systraceLink = testCase.getSystraceList().get(0).getUrl(0).toStringUtf8(); 209 linkList.add(systraceLink); 210 } 211 212 // Process coverage data for test case 213 for (VtsReportMessage.CoverageReportMessage coverage : testCase.getCoverageList()) { 214 CoverageEntity coverageEntity = 215 CoverageEntity.fromCoverageReport(testRunKey, testCaseName, coverage); 216 if (coverageEntity == null) { 217 log.warn("Invalid coverage report in test run " + testRunKey); 218 } else { 219 coveredLineCount += coverageEntity.getCoveredCount(); 220 totalLineCount += coverageEntity.getTotalCount(); 221 coverageEntityList.add(coverageEntity); 222 } 223 } 224 225 // Process profiling data for test case 226 for (VtsReportMessage.ProfilingReportMessage profiling : testCase.getProfilingList()) { 227 ProfilingPointRunEntity profilingPointRunEntity = 228 ProfilingPointRunEntity.fromProfilingReport(testRunKey, profiling); 229 if (profilingPointRunEntity == null) { 230 log.warn("Invalid profiling report in test run " + testRunKey); 231 } else { 232 profilingPointRunEntityList.add(profilingPointRunEntity); 233 profilingPointKeyList.add(profilingPointRunEntity.getKey()); 234 testEntity.setHasProfilingData(true); 235 } 236 } 237 238 TestCaseRunEntity testCaseRunEntity = testCaseRunEntityList.get(testCaseRunEntityIndex); 239 if (!testCaseRunEntity.addTestCase(testCaseName, result.getNumber())) { 240 testCaseRunEntity = new TestCaseRunEntity(); 241 testCaseRunEntity.addTestCase(testCaseName, result.getNumber()); 242 testCaseRunEntityList.add(testCaseRunEntity); 243 testCaseRunEntityIndex++; 244 } 245 } 246 247 // Process device information 248 long testRunType = 0; 249 for (VtsReportMessage.AndroidDeviceInfoMessage device : report.getDeviceInfoList()) { 250 DeviceInfoEntity deviceInfoEntity = 251 DeviceInfoEntity.fromDeviceInfoMessage(testRunKey, device); 252 if (deviceInfoEntity == null) { 253 log.warn("Invalid device info in test run " + testRunKey); 254 } else { 255 // Run type on devices must be the same, else set to OTHER 256 TestRunEntity.TestRunType runType = 257 TestRunEntity.TestRunType.fromBuildId(deviceInfoEntity.getBuildId()); 258 if (runType == null) { 259 testRunType = TestRunEntity.TestRunType.OTHER.getNumber(); 260 } else { 261 testRunType = runType.getNumber(); 262 } 263 deviceInfoEntityList.add(deviceInfoEntity); 264 BuildTargetEntity target = new BuildTargetEntity(deviceInfoEntity.getBuildFlavor()); 265 if (buildTargetKeys.add(target.getKey())) { 266 buildTargetEntityList.add(target); 267 } 268 BranchEntity branch = new BranchEntity(deviceInfoEntity.getBranch()); 269 if (branchKeys.add(branch.getKey())) { 270 branchEntityList.add(branch); 271 } 272 } 273 } 274 275 // Overall run type should be determined by the device builds unless test build is OTHER 276 if (testRunType == TestRunEntity.TestRunType.OTHER.getNumber()) { 277 testRunType = TestRunEntity.TestRunType.fromBuildId(testBuildId).getNumber(); 278 } else if (TestRunEntity.TestRunType.fromBuildId(testBuildId) 279 == TestRunEntity.TestRunType.OTHER) { 280 testRunType = TestRunEntity.TestRunType.OTHER.getNumber(); 281 } 282 283 // Process global coverage data 284 for (VtsReportMessage.CoverageReportMessage coverage : report.getCoverageList()) { 285 CoverageEntity coverageEntity = 286 CoverageEntity.fromCoverageReport(testRunKey, new String(), coverage); 287 if (coverageEntity == null) { 288 log.warn("Invalid coverage report in test run " + testRunKey); 289 } else { 290 coveredLineCount += coverageEntity.getCoveredCount(); 291 totalLineCount += coverageEntity.getTotalCount(); 292 coverageEntityList.add(coverageEntity); 293 } 294 } 295 296 // Process global API coverage data 297 for (VtsReportMessage.ApiCoverageReportMessage apiCoverage : report.getApiCoverageList()) { 298 VtsReportMessage.HalInterfaceMessage halInterfaceMessage = 299 apiCoverage.getHalInterface(); 300 List<String> halApiList = 301 apiCoverage 302 .getHalApiList() 303 .stream() 304 .map(h -> h.toStringUtf8()) 305 .collect(Collectors.toList()); 306 List<String> coveredHalApiList = 307 apiCoverage 308 .getCoveredHalApiList() 309 .stream() 310 .map(h -> h.toStringUtf8()) 311 .collect(Collectors.toList()); 312 ApiCoverageEntity apiCoverageEntity = 313 new ApiCoverageEntity( 314 testRunKey, 315 halInterfaceMessage.getHalPackageName().toStringUtf8(), 316 halInterfaceMessage.getHalVersionMajor(), 317 halInterfaceMessage.getHalVersionMinor(), 318 halInterfaceMessage.getHalInterfaceName().toStringUtf8(), 319 halApiList, 320 coveredHalApiList); 321 apiCoverageEntityList.add(apiCoverageEntity); 322 } 323 324 // Process global profiling data 325 for (VtsReportMessage.ProfilingReportMessage profiling : report.getProfilingList()) { 326 ProfilingPointRunEntity profilingPointRunEntity = 327 ProfilingPointRunEntity.fromProfilingReport(testRunKey, profiling); 328 if (profilingPointRunEntity == null) { 329 log.warn("Invalid profiling report in test run " + testRunKey); 330 } else { 331 profilingPointRunEntityList.add(profilingPointRunEntity); 332 profilingPointKeyList.add(profilingPointRunEntity.getKey()); 333 testEntity.setHasProfilingData(true); 334 } 335 } 336 337 // Process log data 338 for (VtsReportMessage.LogMessage log : report.getLogList()) { 339 if (log.hasUrl()) { 340 linkList.add(log.getUrl().toStringUtf8()); 341 } 342 } 343 // Process url resource 344 for (VtsReportMessage.UrlResourceMessage resource : report.getLinkResourceList()) { 345 if (resource.hasUrl()) { 346 linkList.add(resource.getUrl().toStringUtf8()); 347 } 348 } 349 350 boolean hasCodeCoverage = totalLineCount > 0 && coveredLineCount >= 0; 351 TestRunEntity testRunEntity = 352 new TestRunEntity( 353 testEntity.getOldKey(), 354 testRunType, 355 startTimestamp, 356 endTimestamp, 357 testBuildId, 358 hostName, 359 passCount, 360 failCount, 361 hasCodeCoverage, 362 new ArrayList<>(), 363 linkList); 364 testRunEntityList.add(testRunEntity); 365 366 CodeCoverageEntity codeCoverageEntity = 367 new CodeCoverageEntity( 368 testRunEntity.getId(), 369 testRunEntity.getKey(), 370 coveredLineCount, 371 totalLineCount); 372 codeCoverageEntityList.add(codeCoverageEntity); 373 374 ofy().transact( 375 () -> { 376 List<Long> testCaseIds = new ArrayList<>(); 377 for (Object entity : allEntityList) { 378 if (entity instanceof List) { 379 List listEntity = (List) entity; 380 if (listEntity.size() > 0 381 && listEntity.get(0) instanceof TestCaseRunEntity) { 382 Map< 383 com.googlecode.objectify.Key< 384 TestCaseRunEntity>, 385 TestCaseRunEntity> 386 testCaseRunEntityMap = 387 DashboardEntity.saveAll( 388 testCaseRunEntityList, 389 this 390 .MAX_ENTITY_SIZE_PER_TRANSACTION); 391 392 testCaseIds = 393 testCaseRunEntityMap 394 .values() 395 .stream() 396 .map( 397 testCaseRunEntity -> 398 testCaseRunEntity.getId()) 399 .collect(Collectors.toList()); 400 } else if (listEntity.size() > 0 401 && listEntity.get(0) instanceof TestRunEntity) { 402 testRunEntityList.get(0).setTestCaseIds(testCaseIds); 403 DashboardEntity.saveAll( 404 testRunEntityList, 405 this.MAX_ENTITY_SIZE_PER_TRANSACTION); 406 } else { 407 List<DashboardEntity> dashboardEntityList = 408 (List<DashboardEntity>) entity; 409 DashboardEntity.saveAll( 410 dashboardEntityList, 411 this.MAX_ENTITY_SIZE_PER_TRANSACTION); 412 } 413 } 414 } 415 }); 416 } 417 418 /** 419 * Upload data from a test plan report message 420 * 421 * @param report The test plan report containing data to upload. 422 */ insertTestPlanReport(TestPlanReportMessage report)423 private void insertTestPlanReport(TestPlanReportMessage report) { 424 List<DeviceInfoEntity> deviceInfoEntityList = new ArrayList<>(); 425 List<HalApiEntity> halApiEntityList = new ArrayList<>(); 426 427 List allEntityList = Arrays.asList(deviceInfoEntityList, halApiEntityList); 428 429 List<String> testModules = report.getTestModuleNameList(); 430 List<Long> testTimes = report.getTestModuleStartTimestampList(); 431 if (testModules.size() != testTimes.size() || !report.hasTestPlanName()) { 432 log.error("TestPlanReportMessage is missing information."); 433 return; 434 } 435 436 String testPlanName = report.getTestPlanName(); 437 TestPlanEntity testPlanEntity = new TestPlanEntity(testPlanName); 438 List<com.googlecode.objectify.Key<TestRunEntity>> testRunKeyList = new ArrayList<>(); 439 for (int index = 0; index < testModules.size(); index++) { 440 String test = testModules.get(index); 441 long time = testTimes.get(index); 442 com.googlecode.objectify.Key testKey = 443 com.googlecode.objectify.Key.create(TestEntity.class, test); 444 com.googlecode.objectify.Key testRunKey = 445 com.googlecode.objectify.Key.create(testKey, TestRunEntity.class, time); 446 testRunKeyList.add(testRunKey); 447 } 448 449 Map<com.googlecode.objectify.Key<TestRunEntity>, TestRunEntity> testRunEntityMap = 450 ofy().load().keys(() -> testRunKeyList.iterator()); 451 452 long passCount = 0; 453 long failCount = 0; 454 long startTimestamp = -1; 455 long endTimestamp = -1; 456 String testBuildId = null; 457 long testType = -1; 458 Set<DeviceInfoEntity> deviceInfoEntitySet = new HashSet<>(); 459 for (TestRunEntity testRunEntity : testRunEntityMap.values()) { 460 passCount += testRunEntity.getPassCount(); 461 failCount += testRunEntity.getFailCount(); 462 if (startTimestamp < 0 || testRunEntity.getStartTimestamp() < startTimestamp) { 463 startTimestamp = testRunEntity.getStartTimestamp(); 464 } 465 if (endTimestamp < 0 || testRunEntity.getEndTimestamp() > endTimestamp) { 466 endTimestamp = testRunEntity.getEndTimestamp(); 467 } 468 testType = testRunEntity.getType(); 469 testBuildId = testRunEntity.getTestBuildId(); 470 471 List<DeviceInfoEntity> deviceInfoEntityListWithTestRunKey = 472 ofy().load() 473 .type(DeviceInfoEntity.class) 474 .ancestor(testRunEntity.getOfyKey()) 475 .list(); 476 477 for (DeviceInfoEntity deviceInfoEntity : deviceInfoEntityListWithTestRunKey) { 478 deviceInfoEntitySet.add(deviceInfoEntity); 479 } 480 } 481 482 if (startTimestamp < 0 || testBuildId == null || testType == -1) { 483 log.debug("startTimestamp => " + startTimestamp); 484 log.debug("testBuildId => " + testBuildId); 485 log.debug("type => " + testType); 486 log.error("Couldn't infer test run information from runs."); 487 return; 488 } 489 490 TestPlanRunEntity testPlanRunEntity = 491 new TestPlanRunEntity( 492 testPlanEntity.getKey(), 493 testPlanName, 494 testType, 495 startTimestamp, 496 endTimestamp, 497 testBuildId, 498 passCount, 499 failCount, 500 0L, 501 0L, 502 testRunKeyList); 503 504 // Create the device infos. 505 for (DeviceInfoEntity device : deviceInfoEntitySet) { 506 deviceInfoEntityList.add(device.copyWithParent(testPlanRunEntity.getOfyKey())); 507 } 508 509 // Process global HAL API coverage data 510 for (VtsReportMessage.ApiCoverageReportMessage apiCoverage : report.getHalApiReportList()) { 511 VtsReportMessage.HalInterfaceMessage halInterfaceMessage = 512 apiCoverage.getHalInterface(); 513 List<String> halApiList = 514 apiCoverage 515 .getHalApiList() 516 .stream() 517 .map(h -> h.toStringUtf8()) 518 .collect(Collectors.toList()); 519 List<String> coveredHalApiList = 520 apiCoverage 521 .getCoveredHalApiList() 522 .stream() 523 .map(h -> h.toStringUtf8()) 524 .collect(Collectors.toList()); 525 HalApiEntity halApiEntity = 526 new HalApiEntity( 527 testPlanRunEntity.getOfyKey(), 528 halInterfaceMessage.getHalReleaseLevel().toStringUtf8(), 529 halInterfaceMessage.getHalPackageName().toStringUtf8(), 530 halInterfaceMessage.getHalVersionMajor(), 531 halInterfaceMessage.getHalVersionMinor(), 532 halInterfaceMessage.getHalInterfaceName().toStringUtf8(), 533 halApiList, 534 coveredHalApiList); 535 halApiEntityList.add(halApiEntity); 536 } 537 538 ofy().transact( 539 () -> { 540 testPlanEntity.save(); 541 testPlanRunEntity.save(); 542 for (Object entity : allEntityList) { 543 List<DashboardEntity> dashboardEntityList = 544 (List<DashboardEntity>) entity; 545 Map<com.googlecode.objectify.Key<DashboardEntity>, DashboardEntity> 546 mapInfo = 547 DashboardEntity.saveAll( 548 dashboardEntityList, 549 this.MAX_ENTITY_SIZE_PER_TRANSACTION); 550 } 551 }); 552 553 // Add the task to calculate total number API list. 554 testPlanRunEntity.addCoverageApiTask(); 555 } 556 } 557