1 /* 2 * Copyright (C) 2018 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.tradefed.device.cloud; 17 18 import com.android.tradefed.command.remote.DeviceDescriptor; 19 import com.android.tradefed.device.TestDeviceOptions; 20 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 21 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.result.LogDataType; 24 import com.android.tradefed.result.error.ErrorIdentifier; 25 import com.android.tradefed.result.error.InfraErrorIdentifier; 26 import com.android.tradefed.targetprep.TargetSetupError; 27 import com.android.tradefed.util.CommandResult; 28 import com.android.tradefed.util.CommandStatus; 29 30 import com.google.common.annotations.VisibleForTesting; 31 import com.google.common.base.Strings; 32 import com.google.common.collect.ImmutableMap; 33 import com.google.common.net.HostAndPort; 34 35 import org.json.JSONArray; 36 import org.json.JSONException; 37 import org.json.JSONObject; 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.nio.charset.StandardCharsets; 42 import java.nio.file.Files; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.HashMap; 46 import java.util.LinkedHashMap; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.regex.Matcher; 50 import java.util.regex.Pattern; 51 52 /** Structure to hold relevant data for a given GCE AVD instance. */ 53 public class GceAvdInfo { 54 55 // Patterns to match from Oxygen client's return message to identify error. 56 private static final LinkedHashMap<InfraErrorIdentifier, String> OXYGEN_ERROR_PATTERN_MAP; 57 58 private static final Pattern OXYGEN_LEASE_RESPONSE_PATTERN = 59 Pattern.compile( 60 "session_id:\\s?\"(.*?)\".*?server_url:\\s?\"(.*?)\".*?oxygen_version:\\s?\"(.*?)\"", 61 Pattern.DOTALL); 62 private static final Pattern OXYGENATION_LEASE_RESPONSE_PATTERN = 63 Pattern.compile( 64 "session_id:\\s?\"(.*?)\".*?server_url:\\s?\"(.*?)\".*?oxygen_version:\\s?\"(.*?)\".*?device_id:\\s?\"(.*?)\"", 65 Pattern.DOTALL); 66 67 static { 68 OXYGEN_ERROR_PATTERN_MAP = new LinkedHashMap<InfraErrorIdentifier, String>(); 69 // Order the error message matching carefully so it can surface the expected error properly OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_SERVER_SHUTTING_DOWN, "server_shutting_down")70 OXYGEN_ERROR_PATTERN_MAP.put( 71 InfraErrorIdentifier.OXYGEN_SERVER_SHUTTING_DOWN, "server_shutting_down"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_BAD_GATEWAY_ERROR, "UNAVAILABLE: HTTP status code 502")72 OXYGEN_ERROR_PATTERN_MAP.put( 73 InfraErrorIdentifier.OXYGEN_BAD_GATEWAY_ERROR, "UNAVAILABLE: HTTP status code 502"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_REQUEST_TIMEOUT, "DeadlineExceeded")74 OXYGEN_ERROR_PATTERN_MAP.put( 75 InfraErrorIdentifier.OXYGEN_REQUEST_TIMEOUT, "DeadlineExceeded"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_RESOURCE_EXHAUSTED, "ResourceExhausted")76 OXYGEN_ERROR_PATTERN_MAP.put( 77 InfraErrorIdentifier.OXYGEN_RESOURCE_EXHAUSTED, "ResourceExhausted"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_NOT_ENOUGH_RESOURCE, "Oxygen currently doesn't have enough resources to fulfil this request")78 OXYGEN_ERROR_PATTERN_MAP.put( 79 InfraErrorIdentifier.OXYGEN_NOT_ENOUGH_RESOURCE, 80 "Oxygen currently doesn't have enough resources to fulfil this request"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_SERVER_CONNECTION_FAILURE, "Bad Gateway")81 OXYGEN_ERROR_PATTERN_MAP.put( 82 InfraErrorIdentifier.OXYGEN_SERVER_CONNECTION_FAILURE, "Bad Gateway"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_CLIENT_LEASE_ERROR, "OxygenClient")83 OXYGEN_ERROR_PATTERN_MAP.put( 84 InfraErrorIdentifier.OXYGEN_CLIENT_LEASE_ERROR, "OxygenClient"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_TIMEOUT, "Lease aborted due to launcher failure: Timed out waiting for virtual device to" + " start")85 OXYGEN_ERROR_PATTERN_MAP.put( 86 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_TIMEOUT, 87 "Lease aborted due to launcher failure: Timed out waiting for virtual device to" 88 + " start"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_FAILURE, "Lease aborted due to launcher failure")89 OXYGEN_ERROR_PATTERN_MAP.put( 90 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_FAILURE, 91 "Lease aborted due to launcher failure"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_SERVER_LB_CONNECTION_ERROR, "desc = connection error")92 OXYGEN_ERROR_PATTERN_MAP.put( 93 InfraErrorIdentifier.OXYGEN_SERVER_LB_CONNECTION_ERROR, "desc = connection error"); 94 } 95 96 // Error message for specify Oxygen error. 97 private static final ImmutableMap<InfraErrorIdentifier, String> OXYGEN_ERROR_MESSAGE_MAP = 98 ImmutableMap.of( 99 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_FAILURE, 100 "AVD failed to boot up properly", 101 InfraErrorIdentifier.OXYGEN_SERVER_SHUTTING_DOWN, 102 "Unexpected error from Oxygen service", 103 InfraErrorIdentifier.OXYGEN_BAD_GATEWAY_ERROR, 104 "Unexpected error from Oxygen service", 105 InfraErrorIdentifier.OXYGEN_REQUEST_TIMEOUT, 106 "Unexpected error from Oxygen service. Request timed out.", 107 InfraErrorIdentifier.OXYGEN_RESOURCE_EXHAUSTED, 108 "Oxygen ran out of capacity to lease virtual device", 109 InfraErrorIdentifier.OXYGEN_SERVER_CONNECTION_FAILURE, 110 "Unexpected error from Oxygen service", 111 InfraErrorIdentifier.OXYGEN_CLIENT_LEASE_ERROR, 112 "Oxygen client failed to lease a device", 113 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_TIMEOUT, "AVD boot timed out"); 114 115 public static class LogFileEntry { 116 public final String path; 117 public final LogDataType type; 118 // The name is optional and defaults to an empty string. 119 public final String name; 120 121 @VisibleForTesting LogFileEntry(String path, LogDataType type, String name)122 LogFileEntry(String path, LogDataType type, String name) { 123 this.path = path; 124 this.type = type; 125 this.name = name; 126 } 127 LogFileEntry(JSONObject log)128 LogFileEntry(JSONObject log) throws JSONException { 129 path = log.getString("path"); 130 type = parseLogDataType(log.getString("type")); 131 name = log.optString("name", ""); 132 } 133 parseLogDataType(String typeString)134 private LogDataType parseLogDataType(String typeString) { 135 try { 136 return LogDataType.valueOf(typeString); 137 } catch (IllegalArgumentException e) { 138 CLog.w("Unknown log type in GCE AVD info: %s", typeString); 139 return LogDataType.UNKNOWN; 140 } 141 } 142 } 143 144 public static final List<String> BUILD_VARS = 145 Arrays.asList( 146 "build_id", 147 "build_target", 148 "branch", 149 "kernel_build_id", 150 "kernel_build_target", 151 "kernel_branch", 152 "system_build_id", 153 "system_build_target", 154 "system_branch", 155 "emulator_build_id", 156 "emulator_build_target", 157 "emulator_branch"); 158 159 private String mInstanceName; 160 private HostAndPort mHostAndPort; 161 private ErrorIdentifier mErrorType; 162 private String mErrors; 163 private GceStatus mStatus; 164 private HashMap<String, String> mBuildVars; 165 private List<LogFileEntry> mLogs; 166 private boolean mIsIpPreconfigured = false; 167 private Integer mDeviceOffset = null; 168 private String mInstanceUser = null; 169 // Skip collecting device log if set to true. 170 private boolean mSkipDeviceLogCollection = false; 171 private String mOxygenationDeviceId = null; 172 173 public static enum GceStatus { 174 SUCCESS, 175 FAIL, 176 BOOT_FAIL, 177 DEVICE_OFFLINE, 178 } 179 GceAvdInfo(String instanceName, HostAndPort hostAndPort)180 public GceAvdInfo(String instanceName, HostAndPort hostAndPort) { 181 mInstanceName = instanceName; 182 mHostAndPort = hostAndPort; 183 mBuildVars = new HashMap<String, String>(); 184 mLogs = new ArrayList<LogFileEntry>(); 185 } 186 GceAvdInfo( String instanceName, HostAndPort hostAndPort, ErrorIdentifier errorType, String errors, GceStatus status)187 public GceAvdInfo( 188 String instanceName, 189 HostAndPort hostAndPort, 190 ErrorIdentifier errorType, 191 String errors, 192 GceStatus status) { 193 this(instanceName, hostAndPort); 194 mErrorType = errorType; 195 mErrors = errors; 196 mStatus = status; 197 } 198 199 /** {@inheritDoc} */ 200 @Override toString()201 public String toString() { 202 return "GceAvdInfo [mInstanceName=" 203 + mInstanceName 204 + ", mHostAndPort=" 205 + mHostAndPort 206 + ", mDeviceOffset=" 207 + mDeviceOffset 208 + ", mInstanceUser=" 209 + mInstanceUser 210 + ", mErrorType=" 211 + mErrorType 212 + ", mErrors=" 213 + mErrors 214 + ", mStatus=" 215 + mStatus 216 + ", mIsIpPreconfigured=" 217 + mIsIpPreconfigured 218 + ", mBuildVars=" 219 + ", mOxygenationDeviceId=" 220 + mOxygenationDeviceId 221 + mBuildVars.toString() 222 + ", mLogs=" 223 + mLogs.toString() 224 + "]"; 225 } 226 getOxygenationDeviceId()227 public String getOxygenationDeviceId() { 228 return mOxygenationDeviceId; 229 } 230 setOxygenationDeviceId(String deviceId)231 public void setOxygenationDeviceId(String deviceId) { 232 mOxygenationDeviceId = deviceId; 233 } 234 instanceName()235 public String instanceName() { 236 return mInstanceName; 237 } 238 hostAndPort()239 public HostAndPort hostAndPort() { 240 return mHostAndPort; 241 } 242 getErrorType()243 public ErrorIdentifier getErrorType() { 244 return mErrorType; 245 } 246 setErrorType(ErrorIdentifier errorType)247 public void setErrorType(ErrorIdentifier errorType) { 248 mErrorType = errorType; 249 } 250 getErrors()251 public String getErrors() { 252 return mErrors; 253 } 254 setErrors(String errors)255 public void setErrors(String errors) { 256 mErrors = errors; 257 } 258 259 /** Return the map from local or remote log paths to types. */ getLogs()260 public List<LogFileEntry> getLogs() { 261 return mLogs; 262 } 263 getStatus()264 public GceStatus getStatus() { 265 return mStatus; 266 } 267 setStatus(GceStatus status)268 public void setStatus(GceStatus status) { 269 mStatus = status; 270 } 271 addBuildVar(String buildKey, String buildValue)272 private void addBuildVar(String buildKey, String buildValue) { 273 mBuildVars.put(buildKey, buildValue); 274 } 275 setIpPreconfigured(boolean isIpPreconfigured)276 public void setIpPreconfigured(boolean isIpPreconfigured) { 277 mIsIpPreconfigured = isIpPreconfigured; 278 } 279 isIpPreconfigured()280 public boolean isIpPreconfigured() { 281 return mIsIpPreconfigured; 282 } 283 setDeviceOffset(Integer deviceOffset)284 public void setDeviceOffset(Integer deviceOffset) { 285 mDeviceOffset = deviceOffset; 286 } 287 getDeviceOffset()288 public Integer getDeviceOffset() { 289 return mDeviceOffset; 290 } 291 setInstanceUser(String instanceUser)292 public void setInstanceUser(String instanceUser) { 293 mInstanceUser = instanceUser; 294 } 295 getInstanceUser()296 public String getInstanceUser() { 297 return mInstanceUser; 298 } 299 300 /** 301 * Return build variable information hash of GCE AVD device. 302 * 303 * <p>Possible build variables keys are described in BUILD_VARS for example: build_id, 304 * build_target, branch, kernel_build_id, kernel_build_target, kernel_branch, system_build_id, 305 * system_build_target, system_branch, emulator_build_id, emulator_build_target, 306 * emulator_branch. 307 */ getBuildVars()308 public HashMap<String, String> getBuildVars() { 309 return new HashMap<String, String>(mBuildVars); 310 } 311 getSkipDeviceLogCollection()312 public boolean getSkipDeviceLogCollection() { 313 return mSkipDeviceLogCollection; 314 } 315 316 // TODO(b/329150949): Remove after lab update setSkipBugreportCollection(boolean skipDeviceLogCollection)317 public void setSkipBugreportCollection(boolean skipDeviceLogCollection) { 318 mSkipDeviceLogCollection = skipDeviceLogCollection; 319 } 320 setSkipDeviceLogCollection(boolean skipDeviceLogCollection)321 public void setSkipDeviceLogCollection(boolean skipDeviceLogCollection) { 322 mSkipDeviceLogCollection = skipDeviceLogCollection; 323 } 324 325 /** 326 * Parse a given file to obtain the GCE AVD device info. 327 * 328 * @param f {@link File} file to read the JSON output from GCE Driver. 329 * @param descriptor the descriptor of the device that needs the info. 330 * @param remoteAdbPort the remote port that should be used for adb connection 331 * @return the {@link GceAvdInfo} of the device if found, or null if error. 332 */ parseGceInfoFromFile( File f, DeviceDescriptor descriptor, int remoteAdbPort)333 public static GceAvdInfo parseGceInfoFromFile( 334 File f, DeviceDescriptor descriptor, int remoteAdbPort) throws TargetSetupError { 335 String data; 336 try { 337 data = Files.readString(f.toPath(), StandardCharsets.UTF_8); 338 } catch (IOException e) { 339 CLog.e("Failed to read result file from GCE driver:"); 340 CLog.e(e); 341 return null; 342 } 343 return parseGceInfoFromString(data, descriptor, remoteAdbPort); 344 } 345 346 /** 347 * Parse a given string to obtain the GCE AVD device info. 348 * 349 * @param data JSON string. 350 * @param descriptor the descriptor of the device that needs the info. 351 * @param remoteAdbPort the remote port that should be used for adb connection 352 * @return the {@link GceAvdInfo} of the device if found, or null if error. 353 */ parseGceInfoFromString( String data, DeviceDescriptor descriptor, int remoteAdbPort)354 public static GceAvdInfo parseGceInfoFromString( 355 String data, DeviceDescriptor descriptor, int remoteAdbPort) throws TargetSetupError { 356 if (Strings.isNullOrEmpty(data)) { 357 CLog.w("No data provided"); 358 return null; 359 } 360 InfraErrorIdentifier errorId = null; 361 String errors = data; 362 try { 363 errors = parseErrorField(data); 364 JSONObject res = new JSONObject(data); 365 String status = res.getString("status"); 366 GceStatus gceStatus = GceStatus.valueOf(status); 367 String errorType = res.has("error_type") ? res.getString("error_type") : null; 368 if (errorType == null) { 369 // Parse more detailed error type if we can. 370 if (errors.contains("QUOTA_EXCEED") && errors.contains("GPU")) { 371 errorType = "ACLOUD_QUOTA_EXCEED_GPU"; 372 } 373 } 374 errorId = 375 GceStatus.SUCCESS.equals(gceStatus) 376 ? null 377 : determineAcloudErrorType(errorType); 378 if (errorId == InfraErrorIdentifier.ACLOUD_OXYGEN_LEASE_ERROR) { 379 errorId = refineOxygenErrorType(errors); 380 } 381 JSONArray devices = null; 382 if (GceStatus.FAIL.equals(gceStatus) || GceStatus.BOOT_FAIL.equals(gceStatus)) { 383 // In case of failure we still look for instance name to shutdown if needed. 384 if (res.getJSONObject("data").has("devices_failing_boot")) { 385 devices = res.getJSONObject("data").getJSONArray("devices_failing_boot"); 386 } 387 } else { 388 devices = res.getJSONObject("data").getJSONArray("devices"); 389 } 390 if (devices != null) { 391 if (devices.length() == 1) { 392 JSONObject d = (JSONObject) devices.get(0); 393 // Only log CF boot performance metrics if the device launch is successful. 394 if (GceStatus.SUCCESS.equals(gceStatus)) { 395 addCfStartTimeMetrics(d); 396 } 397 String ip = d.getString("ip"); 398 String instanceName = d.getString("instance_name"); 399 GceAvdInfo avdInfo = 400 new GceAvdInfo( 401 instanceName, 402 HostAndPort.fromString(ip).withDefaultPort(remoteAdbPort), 403 errorId, 404 errors, 405 gceStatus); 406 avdInfo.mLogs.addAll(parseLogField(d)); 407 for (String buildVar : BUILD_VARS) { 408 if (d.has(buildVar) && !d.getString(buildVar).trim().isEmpty()) { 409 avdInfo.addBuildVar(buildVar, d.getString(buildVar).trim()); 410 } 411 } 412 return avdInfo; 413 } else { 414 CLog.w("Expected only one device to return but found %d", devices.length()); 415 } 416 } else { 417 CLog.w("No device information, device was not started."); 418 } 419 } catch (JSONException e) { 420 CLog.e("Failed to parse JSON %s:", data); 421 CLog.e(e); 422 } 423 424 // If errors are found throw an exception with the acloud message. 425 if (errorId == null) { 426 errorId = InfraErrorIdentifier.ACLOUD_UNDETERMINED; 427 } 428 throw new TargetSetupError( 429 String.format("acloud errors: %s", !errors.isEmpty() ? errors : data), 430 descriptor, 431 errorId); 432 } 433 434 /** 435 * Parse a given command line output from Oxygen client binary to obtain leased AVD info. 436 * 437 * @param oxygenRes the {@link CommandResult} from Oxygen client command execution. 438 * @param deviceOptions the {@link TestDeviceOptions} describing the device options 439 * @return {@link List} of the devices successfully leased. Will throw {@link TargetSetupError} 440 * if failed to lease a device. 441 */ parseGceInfoFromOxygenClientOutput( CommandResult oxygenRes, TestDeviceOptions deviceOptions)442 public static List<GceAvdInfo> parseGceInfoFromOxygenClientOutput( 443 CommandResult oxygenRes, TestDeviceOptions deviceOptions) throws TargetSetupError { 444 CommandStatus oxygenCliStatus = oxygenRes.getStatus(); 445 if (CommandStatus.SUCCESS.equals(oxygenCliStatus)) { 446 return parseSucceedOxygenClientOutput( 447 oxygenRes.getStdout() + oxygenRes.getStderr(), deviceOptions); 448 } else if (CommandStatus.TIMED_OUT.equals(oxygenCliStatus)) { 449 return Arrays.asList( 450 new GceAvdInfo( 451 null, 452 null, 453 InfraErrorIdentifier.OXYGEN_CLIENT_BINARY_TIMEOUT, 454 "Oxygen client binary CLI timed out", 455 GceStatus.FAIL)); 456 } else { 457 CLog.d( 458 "OxygenClient - CommandStatus: %s, output: %s\", output", 459 oxygenCliStatus, oxygenRes.getStdout() + " " + oxygenRes.getStderr()); 460 InfraErrorIdentifier identifier = refineOxygenErrorType(oxygenRes.getStderr()); 461 throw new TargetSetupError( 462 OXYGEN_ERROR_MESSAGE_MAP.getOrDefault( 463 identifier, "Oxygen client failed to lease a device"), 464 new Exception( 465 oxygenRes.getStderr()), // Include the original error message as cause. 466 identifier); 467 } 468 } 469 parseSucceedOxygenClientOutput( String output, TestDeviceOptions deviceOptions)470 private static List<GceAvdInfo> parseSucceedOxygenClientOutput( 471 String output, TestDeviceOptions deviceOptions) throws TargetSetupError { 472 CLog.d("Parsing oxygen client output: %s", output); 473 List<GceAvdInfo> gceAvdInfos = new ArrayList<>(); 474 if (deviceOptions.useOxygenationDevice()) { 475 Matcher matcher = OXYGENATION_LEASE_RESPONSE_PATTERN.matcher(output); 476 while (matcher.find()) { 477 String sessionId = matcher.group(1); 478 String serverUrl = matcher.group(2); 479 String oxygenationVersion = matcher.group(3); 480 String deviceId = matcher.group(4); 481 GceAvdInfo gceAvdInfo = 482 new GceAvdInfo( 483 sessionId, 484 HostAndPort.fromString(serverUrl), 485 null, 486 null, 487 GceStatus.SUCCESS); 488 gceAvdInfo.setOxygenationDeviceId(deviceId); 489 gceAvdInfos.add(gceAvdInfo); 490 InvocationMetricLogger.addInvocationMetrics( 491 InvocationMetricKey.CF_OXYGENATION_VERSION, oxygenationVersion); 492 } 493 } else { 494 Matcher matcher = OXYGEN_LEASE_RESPONSE_PATTERN.matcher(output); 495 int deviceOffset = 0; 496 while (matcher.find()) { 497 String sessionId = matcher.group(1); 498 String serverUrl = matcher.group(2); 499 String oxygenVersion = matcher.group(3); 500 gceAvdInfos.add( 501 new GceAvdInfo( 502 sessionId, 503 HostAndPort.fromString(serverUrl) 504 .withDefaultPort( 505 deviceOptions.getRemoteAdbPort() + deviceOffset), 506 null, 507 null, 508 GceStatus.SUCCESS)); 509 InvocationMetricLogger.addInvocationMetrics( 510 InvocationMetricKey.CF_OXYGEN_VERSION, oxygenVersion); 511 deviceOffset++; 512 } 513 } 514 if (gceAvdInfos.isEmpty()) { 515 throw new TargetSetupError( 516 String.format("Failed to parse the output: %s", output), 517 InfraErrorIdentifier.OXYGEN_CLIENT_BINARY_ERROR); 518 } 519 520 return gceAvdInfos; 521 } 522 523 /** 524 * Search error message from Oxygen service for more accurate error code. 525 * 526 * @param errors error messages returned by Oxygen service. 527 * @return InfraErrorIdentifier for the Oxygen service error. 528 */ 529 @VisibleForTesting refineOxygenErrorType(String errors)530 static InfraErrorIdentifier refineOxygenErrorType(String errors) { 531 for (Map.Entry<InfraErrorIdentifier, String> entry : OXYGEN_ERROR_PATTERN_MAP.entrySet()) { 532 if (errors.contains(entry.getValue())) 533 return entry.getKey(); 534 } 535 536 return InfraErrorIdentifier.ACLOUD_OXYGEN_LEASE_ERROR; 537 } 538 parseErrorField(String data)539 private static String parseErrorField(String data) throws JSONException { 540 String res = ""; 541 JSONObject response = new JSONObject(data); 542 JSONArray errors = response.getJSONArray("errors"); 543 for (int i = 0; i < errors.length(); i++) { 544 res += (errors.getString(i) + "\n"); 545 } 546 return res; 547 } 548 549 /** 550 * Parse log paths from a device object. 551 * 552 * @param device the device object in JSON. 553 * @return a list of {@link LogFileEntry}. 554 * @throws JSONException if any required property is missing. 555 */ parseLogField(JSONObject device)556 private static List<LogFileEntry> parseLogField(JSONObject device) throws JSONException { 557 List<LogFileEntry> logs = new ArrayList<LogFileEntry>(); 558 JSONArray logArray = device.optJSONArray("logs"); 559 if (logArray == null) { 560 return logs; 561 } 562 for (int i = 0; i < logArray.length(); i++) { 563 JSONObject logObject = logArray.getJSONObject(i); 564 logs.add(new LogFileEntry(logObject)); 565 } 566 return logs; 567 } 568 569 @VisibleForTesting determineAcloudErrorType(String errorType)570 static InfraErrorIdentifier determineAcloudErrorType(String errorType) { 571 InfraErrorIdentifier identifier; 572 if (errorType == null || errorType.isEmpty()) { 573 return InfraErrorIdentifier.ACLOUD_UNRECOGNIZED_ERROR_TYPE; 574 } 575 try { 576 identifier = InfraErrorIdentifier.valueOf(errorType); 577 } catch (Exception e) { 578 identifier = InfraErrorIdentifier.ACLOUD_UNRECOGNIZED_ERROR_TYPE; 579 } 580 return identifier; 581 } 582 583 @VisibleForTesting addCfStartTimeMetrics(JSONObject json)584 static void addCfStartTimeMetrics(JSONObject json) { 585 // These metrics may not be available for all GCE. 586 String fetch_artifact_time = json.optString("fetch_artifact_time"); 587 if (!fetch_artifact_time.isEmpty()) { 588 InvocationMetricLogger.addInvocationMetrics( 589 InvocationMetricKey.CF_FETCH_ARTIFACT_TIME, 590 Double.valueOf(Double.parseDouble(fetch_artifact_time) * 1000).longValue()); 591 } 592 String gce_create_time = json.optString("gce_create_time"); 593 if (!gce_create_time.isEmpty()) { 594 InvocationMetricLogger.addInvocationMetrics( 595 InvocationMetricKey.CF_GCE_CREATE_TIME, 596 Double.valueOf(Double.parseDouble(gce_create_time) * 1000).longValue()); 597 } 598 String launch_cvd_time = json.optString("launch_cvd_time"); 599 if (!launch_cvd_time.isEmpty()) { 600 InvocationMetricLogger.addInvocationMetrics( 601 InvocationMetricKey.CF_LAUNCH_CVD_TIME, 602 Double.valueOf(Double.parseDouble(launch_cvd_time) * 1000).longValue()); 603 } 604 JSONObject fetch_cvd_wrapper_log = json.optJSONObject("fetch_cvd_wrapper_log"); 605 if (fetch_cvd_wrapper_log != null) { 606 String cf_cache_wait_time_sec = 607 fetch_cvd_wrapper_log.optString("cf_cache_wait_time_sec"); 608 if (!Strings.isNullOrEmpty(cf_cache_wait_time_sec)) { 609 InvocationMetricLogger.addInvocationMetrics( 610 InvocationMetricKey.CF_CACHE_WAIT_TIME, 611 Integer.parseInt(cf_cache_wait_time_sec)); 612 } 613 String cf_artifacts_fetch_source = 614 fetch_cvd_wrapper_log.optString("cf_artifacts_fetch_source"); 615 if (!Strings.isNullOrEmpty(cf_artifacts_fetch_source)) { 616 InvocationMetricLogger.addInvocationMetrics( 617 InvocationMetricKey.CF_ARTIFACTS_FETCH_SOURCE, cf_artifacts_fetch_source); 618 } 619 } 620 621 if (!InvocationMetricLogger.getInvocationMetrics() 622 .containsKey(InvocationMetricKey.CF_INSTANCE_COUNT.toString())) { 623 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CF_INSTANCE_COUNT, 1); 624 } 625 } 626 } 627