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