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.invoker.logger.InvocationMetricLogger; 20 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.targetprep.TargetSetupError; 23 import com.android.tradefed.util.FileUtil; 24 25 import com.google.common.annotations.VisibleForTesting; 26 import com.google.common.base.Strings; 27 import com.google.common.net.HostAndPort; 28 29 import org.json.JSONArray; 30 import org.json.JSONException; 31 import org.json.JSONObject; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.util.Arrays; 36 import java.util.HashMap; 37 import java.util.List; 38 39 /** Structure to hold relevant data for a given GCE AVD instance. */ 40 public class GceAvdInfo { 41 42 public static final List<String> BUILD_VARS = 43 Arrays.asList( 44 "build_id", 45 "build_target", 46 "branch", 47 "kernel_build_id", 48 "kernel_build_target", 49 "kernel_branch", 50 "system_build_id", 51 "system_build_target", 52 "system_branch", 53 "emulator_build_id", 54 "emulator_build_target", 55 "emulator_branch"); 56 57 private String mInstanceName; 58 private HostAndPort mHostAndPort; 59 private String mErrors; 60 private GceStatus mStatus; 61 private HashMap<String, String> mBuildVars; 62 63 public static enum GceStatus { 64 SUCCESS, 65 FAIL, 66 BOOT_FAIL, 67 DEVICE_OFFLINE, 68 } 69 GceAvdInfo(String instanceName, HostAndPort hostAndPort)70 public GceAvdInfo(String instanceName, HostAndPort hostAndPort) { 71 mInstanceName = instanceName; 72 mHostAndPort = hostAndPort; 73 mBuildVars = new HashMap<String, String>(); 74 } 75 GceAvdInfo( String instanceName, HostAndPort hostAndPort, String errors, GceStatus status)76 public GceAvdInfo( 77 String instanceName, HostAndPort hostAndPort, String errors, GceStatus status) { 78 mInstanceName = instanceName; 79 mHostAndPort = hostAndPort; 80 mErrors = errors; 81 mStatus = status; 82 mBuildVars = new HashMap<String, String>(); 83 } 84 85 /** {@inheritDoc} */ 86 @Override toString()87 public String toString() { 88 return "GceAvdInfo [mInstanceName=" 89 + mInstanceName 90 + ", mHostAndPort=" 91 + mHostAndPort 92 + ", mErrors=" 93 + mErrors 94 + ", mStatus=" 95 + mStatus 96 + ", mBuildVars=" 97 + mBuildVars.toString() 98 + "]"; 99 } 100 instanceName()101 public String instanceName() { 102 return mInstanceName; 103 } 104 hostAndPort()105 public HostAndPort hostAndPort() { 106 return mHostAndPort; 107 } 108 getErrors()109 public String getErrors() { 110 return mErrors; 111 } 112 getStatus()113 public GceStatus getStatus() { 114 return mStatus; 115 } 116 setStatus(GceStatus status)117 public void setStatus(GceStatus status) { 118 mStatus = status; 119 } 120 addBuildVar(String buildKey, String buildValue)121 private void addBuildVar(String buildKey, String buildValue) { 122 mBuildVars.put(buildKey, buildValue); 123 } 124 125 /** 126 * Return build variable information hash of GCE AVD device. 127 * 128 * <p>Possible build variables keys are described in BUILD_VARS for example: build_id, 129 * build_target, branch, kernel_build_id, kernel_build_target, kernel_branch, system_build_id, 130 * system_build_target, system_branch, emulator_build_id, emulator_build_target, 131 * emulator_branch. 132 */ getBuildVars()133 public HashMap<String, String> getBuildVars() { 134 return new HashMap<String, String>(mBuildVars); 135 } 136 137 /** 138 * Parse a given file to obtain the GCE AVD device info. 139 * 140 * @param f {@link File} file to read the JSON output from GCE Driver. 141 * @param descriptor the descriptor of the device that needs the info. 142 * @param remoteAdbPort the remote port that should be used for adb connection 143 * @return the {@link GceAvdInfo} of the device if found, or null if error. 144 */ parseGceInfoFromFile( File f, DeviceDescriptor descriptor, int remoteAdbPort)145 public static GceAvdInfo parseGceInfoFromFile( 146 File f, DeviceDescriptor descriptor, int remoteAdbPort) throws TargetSetupError { 147 String data; 148 try { 149 data = FileUtil.readStringFromFile(f); 150 } catch (IOException e) { 151 CLog.e("Failed to read result file from GCE driver:"); 152 CLog.e(e); 153 return null; 154 } 155 return parseGceInfoFromString(data, descriptor, remoteAdbPort); 156 } 157 158 /** 159 * Parse a given string to obtain the GCE AVD device info. 160 * 161 * @param data JSON string. 162 * @param descriptor the descriptor of the device that needs the info. 163 * @param remoteAdbPort the remote port that should be used for adb connection 164 * @return the {@link GceAvdInfo} of the device if found, or null if error. 165 */ parseGceInfoFromString( String data, DeviceDescriptor descriptor, int remoteAdbPort)166 public static GceAvdInfo parseGceInfoFromString( 167 String data, DeviceDescriptor descriptor, int remoteAdbPort) throws TargetSetupError { 168 if (Strings.isNullOrEmpty(data)) { 169 CLog.w("No data provided"); 170 return null; 171 } 172 String errors = data; 173 try { 174 errors = parseErrorField(data); 175 JSONObject res = new JSONObject(data); 176 String status = res.getString("status"); 177 JSONArray devices = null; 178 GceStatus gceStatus = GceStatus.valueOf(status); 179 if (GceStatus.FAIL.equals(gceStatus) || GceStatus.BOOT_FAIL.equals(gceStatus)) { 180 // In case of failure we still look for instance name to shutdown if needed. 181 if (res.getJSONObject("data").has("devices_failing_boot")) { 182 devices = res.getJSONObject("data").getJSONArray("devices_failing_boot"); 183 } 184 } else { 185 devices = res.getJSONObject("data").getJSONArray("devices"); 186 } 187 if (devices != null) { 188 if (devices.length() == 1) { 189 JSONObject d = (JSONObject) devices.get(0); 190 addCfStartTimeMetrics(d); 191 String ip = d.getString("ip"); 192 String instanceName = d.getString("instance_name"); 193 GceAvdInfo avdInfo = 194 new GceAvdInfo( 195 instanceName, 196 HostAndPort.fromString(ip).withDefaultPort(remoteAdbPort), 197 errors, 198 gceStatus); 199 for (String buildVar : BUILD_VARS) { 200 if (d.has(buildVar) && !d.getString(buildVar).trim().isEmpty()) { 201 avdInfo.addBuildVar(buildVar, d.getString(buildVar).trim()); 202 } 203 } 204 return avdInfo; 205 } else { 206 CLog.w("Expected only one device to return but found %d", devices.length()); 207 } 208 } else { 209 CLog.w("No device information, device was not started."); 210 } 211 } catch (JSONException e) { 212 CLog.e("Failed to parse JSON %s:", data); 213 CLog.e(e); 214 } 215 // If errors are found throw an exception with the acloud message. 216 if (errors.isEmpty()) { 217 throw new TargetSetupError(String.format("acloud errors: %s", data), descriptor); 218 } else { 219 throw new TargetSetupError(String.format("acloud errors: %s", errors), descriptor); 220 } 221 } 222 parseErrorField(String data)223 private static String parseErrorField(String data) throws JSONException { 224 String res = ""; 225 JSONObject response = new JSONObject(data); 226 JSONArray errors = response.getJSONArray("errors"); 227 for (int i = 0; i < errors.length(); i++) { 228 res += (errors.getString(i) + "\n"); 229 } 230 return res; 231 } 232 233 @VisibleForTesting addCfStartTimeMetrics(JSONObject json)234 static void addCfStartTimeMetrics(JSONObject json) { 235 // These metrics may not be available for all GCE. 236 String fetch_artifact_time = json.optString("fetch_artifact_time"); 237 if (!fetch_artifact_time.isEmpty()) { 238 InvocationMetricLogger.addInvocationMetrics( 239 InvocationMetricKey.CF_FETCH_ARTIFACT_TIME, 240 Double.valueOf(Double.parseDouble(fetch_artifact_time) * 1000).longValue()); 241 } 242 String gce_create_time = json.optString("gce_create_time"); 243 if (!gce_create_time.isEmpty()) { 244 InvocationMetricLogger.addInvocationMetrics( 245 InvocationMetricKey.CF_GCE_CREATE_TIME, 246 Double.valueOf(Double.parseDouble(gce_create_time) * 1000).longValue()); 247 } 248 String launch_cvd_time = json.optString("launch_cvd_time"); 249 if (!launch_cvd_time.isEmpty()) { 250 InvocationMetricLogger.addInvocationMetrics( 251 InvocationMetricKey.CF_LAUNCH_CVD_TIME, 252 Double.valueOf(Double.parseDouble(launch_cvd_time) * 1000).longValue()); 253 } 254 if (!InvocationMetricLogger.getInvocationMetrics() 255 .containsKey(InvocationMetricKey.CF_INSTANCE_COUNT.toString())) { 256 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CF_INSTANCE_COUNT, 1); 257 } 258 } 259 } 260