• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.gcs;
18 
19 import com.android.tradefed.log.LogUtil.CLog;
20 
21 import com.google.api.client.auth.oauth2.Credential;
22 import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
23 import com.google.api.client.http.HttpRequest;
24 import com.google.api.client.http.HttpRequestInitializer;
25 import com.google.api.client.http.HttpResponse;
26 import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
27 import com.google.api.client.util.ExponentialBackOff;
28 import com.google.auth.Credentials;
29 import com.google.auth.oauth2.ComputeEngineCredentials;
30 import com.google.auth.oauth2.GoogleCredentials;
31 import com.google.common.annotations.VisibleForTesting;
32 
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.FileNotFoundException;
36 import java.io.IOException;
37 import java.security.GeneralSecurityException;
38 import java.util.Collection;
39 
40 public class GoogleApiClientUtilBase {
41 
42     public static final String APP_NAME = "tradefed";
43     private static GoogleApiClientUtilBase sInstance = null;
44 
getInstance()45     private static GoogleApiClientUtilBase getInstance() {
46         if (sInstance == null) {
47             sInstance = new GoogleApiClientUtilBase();
48         }
49         return sInstance;
50     }
51 
52     /**
53      * Try to create Google API credential with default credential.
54      *
55      * <p>Only default credential is used.
56      *
57      * @param scopes scopes for the credential.
58      * @return a {@link Credential}
59      * @throws IOException
60      * @throws GeneralSecurityException
61      */
createCredential(Collection<String> scopes)62     public static Credentials createCredential(Collection<String> scopes)
63             throws IOException, GeneralSecurityException {
64         return getInstance().doCreateDefaultCredential(scopes);
65     }
66 
67     /**
68      * Create credential from json key file.
69      *
70      * @param file is the p12 key file
71      * @param scopes is the API's scope.
72      * @return a {@link Credential}.
73      * @throws FileNotFoundException
74      * @throws IOException
75      * @throws GeneralSecurityException
76      */
createCredentialFromJsonKeyFile(File file, Collection<String> scopes)77     public static Credentials createCredentialFromJsonKeyFile(File file, Collection<String> scopes)
78             throws IOException, GeneralSecurityException {
79         return getInstance().doCreateCredentialFromJsonKeyFile(file, scopes);
80     }
81 
82     @VisibleForTesting
doCreateCredentialFromJsonKeyFile(File file, Collection<String> scopes)83     protected Credentials doCreateCredentialFromJsonKeyFile(File file, Collection<String> scopes)
84             throws IOException, GeneralSecurityException {
85         Credentials credentail =
86                 GoogleCredentials.fromStream(new FileInputStream(file)).createScoped(scopes);
87         return credentail;
88     }
89 
90     @VisibleForTesting
doCreateDefaultCredential(Collection<String> scopes)91     protected Credentials doCreateDefaultCredential(Collection<String> scopes) throws IOException {
92         try {
93             CLog.d("Using local authentication.");
94             return ComputeEngineCredentials.getApplicationDefault().createScoped(scopes);
95         } catch (IOException e) {
96             CLog.e(
97                     "Try 'gcloud auth application-default login' to login for "
98                             + "personal account; Or 'export "
99                             + "GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json' "
100                             + "for service account.");
101             throw e;
102         }
103     }
104 
105     /**
106      * @param requestInitializer a {@link HttpRequestInitializer}, normally it's {@link Credential}.
107      * @param connectTimeout connect timeout in milliseconds.
108      * @param readTimeout read timeout in milliseconds.
109      * @return a {@link HttpRequestInitializer} with timeout.
110      */
setHttpTimeout( final HttpRequestInitializer requestInitializer, int connectTimeout, int readTimeout)111     public static HttpRequestInitializer setHttpTimeout(
112             final HttpRequestInitializer requestInitializer, int connectTimeout, int readTimeout) {
113         return new HttpRequestInitializer() {
114             @Override
115             public void initialize(HttpRequest request) throws IOException {
116                 requestInitializer.initialize(request);
117                 request.setConnectTimeout(connectTimeout);
118                 request.setReadTimeout(readTimeout);
119             }
120         };
121     }
122 
123     /**
124      * Setup a retry strategy for the provided HttpRequestInitializer. In case of server errors
125      * requests will be automatically retried with an exponential backoff.
126      *
127      * @param initializer - an initializer which will setup a retry strategy.
128      * @return an initializer that will retry failed requests automatically.
129      */
130     public static HttpRequestInitializer configureRetryStrategyAndTimeout(
131             HttpRequestInitializer initializer, int connectTimeout, int readTimeout) {
132         return new HttpRequestInitializer() {
133             @Override
134             public void initialize(HttpRequest request) throws IOException {
135                 initializer.initialize(request);
136                 request.setConnectTimeout(connectTimeout);
137                 request.setReadTimeout(readTimeout);
138                 request.setUnsuccessfulResponseHandler(new RetryResponseHandler());
139             }
140         };
141     }
142 
143     /**
144      * Setup a retry strategy for the provided HttpRequestInitializer. In case of server errors
145      * requests will be automatically retried with an exponential backoff.
146      *
147      * @param initializer - an initializer which will setup a retry strategy.
148      * @return an initializer that will retry failed requests automatically.
149      */
150     public static HttpRequestInitializer configureRetryStrategy(
151             HttpRequestInitializer initializer) {
152         return new HttpRequestInitializer() {
153             @Override
154             public void initialize(HttpRequest request) throws IOException {
155                 initializer.initialize(request);
156                 request.setUnsuccessfulResponseHandler(new RetryResponseHandler());
157             }
158         };
159     }
160 
161     private static class RetryResponseHandler implements HttpUnsuccessfulResponseHandler {
162         // Initial interval to wait before retrying if a request fails.
163         private static final int INITIAL_RETRY_INTERVAL = 1000;
164         private static final int MAX_RETRY_INTERVAL = 3 * 60000; // Set max interval to 3 minutes.
165 
166         private final HttpUnsuccessfulResponseHandler backOffHandler;
167 
168         public RetryResponseHandler() {
169             backOffHandler =
170                     new HttpBackOffUnsuccessfulResponseHandler(
171                             new ExponentialBackOff.Builder()
172                                     .setInitialIntervalMillis(INITIAL_RETRY_INTERVAL)
173                                     .setMaxIntervalMillis(MAX_RETRY_INTERVAL)
174                                     .build());
175         }
176 
177         @Override
178         public boolean handleResponse(
179                 HttpRequest request, HttpResponse response, boolean supportsRetry)
180                 throws IOException {
181             CLog.w(
182                     "Request to %s failed: %d %s",
183                     request.getUrl(), response.getStatusCode(), response.getStatusMessage());
184             if (response.getStatusCode() == 400) {
185                 return false;
186             }
187             return backOffHandler.handleResponse(request, response, supportsRetry);
188         }
189     }
190 }
191