1 /* 2 * Copyright (C) 2017 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 17 package com.android.tradefed.util; 18 19 import com.google.common.base.Strings; 20 import java.io.File; 21 import java.io.FileNotFoundException; 22 import java.io.IOException; 23 import java.io.FileInputStream; 24 import java.io.FileOutputStream; 25 import java.util.ArrayList; 26 import java.util.Base64; 27 import java.util.List; 28 import java.util.LinkedList; 29 import java.util.NoSuchElementException; 30 import java.util.Optional; 31 import java.util.regex.Pattern; 32 import java.util.regex.Matcher; 33 34 import com.android.tradefed.log.LogUtil.CLog; 35 36 import com.google.api.client.auth.oauth2.Credential; 37 import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 38 import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; 39 import com.google.api.client.json.jackson2.JacksonFactory; 40 import com.google.api.client.json.JsonFactory; 41 42 import com.android.vts.proto.VtsReportMessage.DashboardPostMessage; 43 import com.android.vts.proto.VtsReportMessage.TestPlanReportMessage; 44 45 import com.google.api.client.http.javanet.NetHttpTransport; 46 47 /** 48 * Uploads the VTS test plan execution result to the web DB using a RESTful API and an OAuth2 49 * credential kept in a json file. 50 */ 51 public class VtsDashboardUtil { 52 private static final String PLUS_ME = "https://www.googleapis.com/auth/plus.me"; 53 private static final int BASE_TIMEOUT_MSECS = 1000 * 60; 54 private static VtsVendorConfigFileUtil mConfigReader; 55 private static final IRunUtil mRunUtil = new RunUtil(); 56 private static VtsDashboardApiTransport vtsDashboardApiTransport; 57 VtsDashboardUtil(VtsVendorConfigFileUtil configReader)58 public VtsDashboardUtil(VtsVendorConfigFileUtil configReader) { 59 mConfigReader = configReader; 60 try { 61 String apiUrl = mConfigReader.GetVendorConfigVariable("dashboard_api_host_url"); 62 vtsDashboardApiTransport = new VtsDashboardApiTransport(new NetHttpTransport(), apiUrl); 63 } catch (NoSuchElementException e) { 64 CLog.w("Configure file not available."); 65 } 66 } 67 68 /** 69 * Returns an OAuth2 token string obtained using a service account json keyfile. 70 * 71 * Uses the service account keyfile located at config variable 'service_key_json_path' 72 * to request an OAuth2 token. 73 */ GetToken()74 private String GetToken() { 75 String keyFilePath; 76 try { 77 keyFilePath = mConfigReader.GetVendorConfigVariable("service_key_json_path"); 78 } catch (NoSuchElementException e) { 79 return null; 80 } 81 82 JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); 83 Credential credential = null; 84 try { 85 List<String> listStrings = new LinkedList<>(); 86 listStrings.add(PLUS_ME); 87 credential = GoogleCredential.fromStream(new FileInputStream(keyFilePath)) 88 .createScoped(listStrings); 89 credential.refreshToken(); 90 return credential.getAccessToken(); 91 } catch (FileNotFoundException e) { 92 CLog.e(String.format("Service key file %s doesn't exist.", keyFilePath)); 93 } catch (IOException e) { 94 CLog.e(String.format("Can't read the service key file, %s", keyFilePath)); 95 } 96 return null; 97 } 98 99 /** 100 * Uploads the given message to the web DB. 101 * 102 * @param message, DashboardPostMessage that keeps the result to upload. 103 */ Upload(DashboardPostMessage.Builder message)104 public void Upload(DashboardPostMessage.Builder message) { 105 String dashboardCurlCommand = 106 mConfigReader.GetVendorConfigVariable("dashboard_use_curl_command"); 107 Optional<String> dashboardCurlCommandOpt = Optional.of(dashboardCurlCommand); 108 Boolean curlCommandCheck = Boolean.parseBoolean(dashboardCurlCommandOpt.orElse("false")); 109 String token = GetToken(); 110 if (token == null) { 111 return; 112 } 113 message.setAccessToken(token); 114 String messageFilePath = ""; 115 try { 116 messageFilePath = WriteToTempFile( 117 Base64.getEncoder().encodeToString(message.build().toByteArray()).getBytes()); 118 } catch (IOException e) { 119 CLog.e("Couldn't write a proto message to a temp file."); 120 } 121 122 if (Strings.isNullOrEmpty(messageFilePath)) { 123 CLog.e("Couldn't get the MessageFilePath."); 124 } else { 125 if (curlCommandCheck) { 126 CurlUpload(messageFilePath); 127 } else { 128 Upload(messageFilePath); 129 } 130 } 131 } 132 133 /** 134 * Uploads the given message file path to the web DB using google http java api library. 135 * 136 * @param messageFilePath, DashboardPostMessage file path that keeps the result to upload. 137 */ Upload(String messageFilePath)138 public Boolean Upload(String messageFilePath) { 139 try { 140 String response = vtsDashboardApiTransport.postFile( 141 "/api/datastore", "application/octet-stream", messageFilePath); 142 CLog.d(String.format("Upload Result : %s", response)); 143 return true; 144 } catch (IOException e) { 145 CLog.e("Error occurred on uploading dashboard message file!"); 146 CLog.e(e.getLocalizedMessage()); 147 return false; 148 } 149 } 150 151 /** 152 * Uploads the given message file path to the web DB using curl command. 153 * 154 * @param messageFilePath, DashboardPostMessage file path that keeps the result to upload. 155 */ 156 @Deprecated CurlUpload(String messageFilePath)157 public void CurlUpload(String messageFilePath) { 158 try { 159 String commandTemplate = 160 mConfigReader.GetVendorConfigVariable("dashboard_post_command"); 161 commandTemplate = commandTemplate.replace("{path}", messageFilePath); 162 // removes ', while keeping any substrings quoted by "". 163 commandTemplate = commandTemplate.replace("'", ""); 164 CLog.d(String.format("Upload command: %s", commandTemplate)); 165 List<String> commandList = new ArrayList<String>(); 166 Matcher matcher = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(commandTemplate); 167 while (matcher.find()) { 168 commandList.add(matcher.group(1)); 169 } 170 CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT_MSECS * 3, 171 (String[]) commandList.toArray(new String[commandList.size()])); 172 if (c == null || c.getStatus() != CommandStatus.SUCCESS) { 173 CLog.e("Uploading the test plan execution result to GAE DB faiied."); 174 CLog.e("Stdout: %s", c.getStdout()); 175 CLog.e("Stderr: %s", c.getStderr()); 176 } 177 FileUtil.deleteFile(new File(messageFilePath)); 178 } catch (NoSuchElementException e) { 179 CLog.e("dashboard_post_command unspecified in vendor config."); 180 } 181 } 182 183 /** 184 * Simple wrapper to write data to a temp file. 185 * 186 * @param data, actual data to write to a file. 187 * @throws IOException 188 */ WriteToTempFile(byte[] data)189 private String WriteToTempFile(byte[] data) throws IOException { 190 File tempFile = File.createTempFile("tempfile", ".tmp"); 191 String filePath = tempFile.getAbsolutePath(); 192 FileOutputStream out = new FileOutputStream(filePath); 193 out.write(data); 194 out.close(); 195 return filePath; 196 } 197 } 198