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