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 package com.android.compatibility.common.tradefed.targetprep;
17 
18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
19 import com.android.compatibility.common.tradefed.util.DynamicConfigFileReader;
20 import com.android.compatibility.common.util.BusinessLogic;
21 import com.android.compatibility.common.util.BusinessLogicFactory;
22 import com.android.compatibility.common.util.FeatureUtil;
23 import com.android.compatibility.common.util.PropertyUtil;
24 import com.android.tradefed.build.IBuildInfo;
25 import com.android.tradefed.config.GlobalConfiguration;
26 import com.android.tradefed.config.Option;
27 import com.android.tradefed.config.OptionClass;
28 import com.android.tradefed.device.DeviceNotAvailableException;
29 import com.android.tradefed.device.ITestDevice;
30 import com.android.tradefed.invoker.IInvocationContext;
31 import com.android.tradefed.invoker.TestInformation;
32 import com.android.tradefed.log.LogUtil.CLog;
33 import com.android.tradefed.targetprep.BaseTargetPreparer;
34 import com.android.tradefed.targetprep.BuildError;
35 import com.android.tradefed.targetprep.TargetSetupError;
36 import com.android.tradefed.testtype.IAbi;
37 import com.android.tradefed.testtype.IAbiReceiver;
38 import com.android.tradefed.testtype.IInvocationContextReceiver;
39 import com.android.tradefed.testtype.suite.TestSuiteInfo;
40 import com.android.tradefed.util.FileUtil;
41 import com.android.tradefed.util.MultiMap;
42 import com.android.tradefed.util.RunUtil;
43 import com.android.tradefed.util.StreamUtil;
44 import com.android.tradefed.util.net.HttpHelper;
45 import com.android.tradefed.util.net.IHttpHelper;
46 
47 import com.google.api.client.auth.oauth2.Credential;
48 import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
49 import com.google.common.annotations.VisibleForTesting;
50 import com.google.common.base.Strings;
51 
52 import org.json.JSONException;
53 import org.json.JSONObject;
54 import org.xmlpull.v1.XmlPullParserException;
55 
56 import java.io.DataOutputStream;
57 import java.io.File;
58 import java.io.FileInputStream;
59 import java.io.FileNotFoundException;
60 import java.io.IOException;
61 import java.net.HttpURLConnection;
62 import java.net.URL;
63 import java.util.ArrayList;
64 import java.util.Collections;
65 import java.util.Date;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Set;
69 
70 /**
71  * Pushes business Logic to the host and the test device, for use by test cases in the test suite.
72  */
73 @OptionClass(alias = "business-logic-preparer")
74 public class BusinessLogicPreparer extends BaseTargetPreparer
75         implements IAbiReceiver, IInvocationContextReceiver {
76 
77     /* Placeholder in the service URL for the suite to be configured */
78     private static final String SUITE_PLACEHOLDER = "{suite-name}";
79 
80     /* String for the key to get file from GlobalConfiguration */
81     private static final String GLOBAL_APE_API_KEY = "ape-api-key";
82 
83     /* String for creating files to store the business logic configuration on the host */
84     private static final String FILE_LOCATION = "business-logic";
85     /* String for creating cached business logic configuration files */
86     private static final String BL_CACHE_FILE = "business-logic-cache";
87     /* Number of days for which cached business logic is valid */
88     private static final int BL_CACHE_DAYS = 5;
89     /* BL_CACHE_DAYS converted to millis */
90     private static final long BL_CACHE_MILLIS = BL_CACHE_DAYS * 1000 * 60 * 60 * 24L;
91     /* Extension of business logic files */
92     private static final String FILE_EXT = ".bl";
93     /* Default amount of time to attempt connection to the business logic service, in seconds */
94     private static final int DEFAULT_CONNECTION_TIME = 60;
95     /* Time to wait between connection attempts to the business logic service, in millis */
96     private static final long SLEEP_BETWEEN_CONNECTIONS_MS = 5000; // 5 seconds
97     /* Dynamic config constants */
98     private static final String DYNAMIC_CONFIG_FEATURES_KEY = "business_logic_device_features";
99     private static final String DYNAMIC_CONFIG_PROPERTIES_KEY = "business_logic_device_properties";
100     private static final String DYNAMIC_CONFIG_PACKAGES_KEY = "business_logic_device_packages";
101     private static final String DYNAMIC_CONFIG_EXTENDED_DEVICE_INFO_KEY =
102             "business_logic_extended_device_info";
103 
104     @Option(name = "business-logic-url", description = "The URL to use when accessing the " +
105             "business logic service, parameters not included", mandatory = true)
106     private String mUrl;
107 
108     @Option(name = "business-logic-api-key", description = "The API key to use when accessing " +
109             "the business logic service.", mandatory = true)
110     private String mApiKey;
111 
112     @Option(name = "business-logic-api-scope", description = "The URI of api scope to use when " +
113             "retrieving business logic rules.")
114     /* URI of api scope to use when retrieving business logic rules */
115     private  String mApiScope;
116 
117     @Option(name = "cache-business-logic", description = "Whether to keep and use cached " +
118             "business logic files.")
119     private boolean mCache = false;
120 
121     @Option(name = "clean-cache-business-logic", description = "Like option " +
122             "'cache-business-logic', but forces a refresh of the cached business logic file")
123     private boolean mCleanCache = false;
124 
125     @Option(name = "ignore-business-logic-failure", description = "Whether to proceed with the " +
126             "suite invocation if retrieval of business logic fails.")
127     private boolean mIgnoreFailure = false;
128 
129     @Option(name = "business-logic-connection-time", description = "Amount of time to attempt " +
130             "connection to the business logic service, in seconds.")
131     private int mMaxConnectionTime = DEFAULT_CONNECTION_TIME;
132 
133     @Option(name = "config-filename", description = "The module name for module-level " +
134             "configurations, or the suite name for suite-level configurations. Will lookup " +
135             "suite name if not provided.")
136     private String mModuleName = null;
137 
138     @Option(name = "version", description = "The module configuration version to retrieve.")
139     private String mModuleVersion = null;
140 
141     private String mDeviceFilePushed;
142     private String mHostFilePushed;
143     private IAbi mAbi = null;
144     private IInvocationContext mModuleContext = null;
145 
146     /** {@inheritDoc} */
147     @Override
setAbi(IAbi abi)148     public void setAbi(IAbi abi) {
149         mAbi = abi;
150     }
151 
152     /** {@inheritDoc} */
153     @Override
getAbi()154     public IAbi getAbi() {
155         return mAbi;
156     }
157 
158 
159     /** {@inheritDoc} */
160     @Override
setInvocationContext(IInvocationContext invocationContext)161     public void setInvocationContext(IInvocationContext invocationContext) {
162         mModuleContext = invocationContext;
163     }
164 
165     /** {@inheritDoc} */
166     @Override
setUp(TestInformation testInfo)167     public void setUp(TestInformation testInfo)
168             throws TargetSetupError, BuildError, DeviceNotAvailableException {
169         IBuildInfo buildInfo = testInfo.getBuildInfo();
170         ITestDevice device = testInfo.getDevice();
171         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
172         if (buildHelper.hasBusinessLogicHostFile()) {
173             CLog.i("Business logic file already collected, skipping BusinessLogicPreparer.");
174             return;
175         }
176         // Ensure mModuleName is set.
177         if (mModuleName == null) {
178             mModuleName = "";
179             CLog.w("Option config-filename isn't set. Using empty string instead.");
180         }
181         if (mModuleVersion == null) {
182             CLog.w("Option version isn't set. Using 'null' instead.");
183             mModuleVersion = "null";
184         }
185         String requestParams = buildRequestParams(device, buildInfo);
186         String baseUrl = mUrl.replace(SUITE_PLACEHOLDER, getSuiteName());
187         String businessLogicString = null;
188         // use cached business logic string if options are set accordingly and cache is valid,
189         // otherwise proceed with remote download.
190         if (!shouldReadCache()
191                 || (businessLogicString = readFromCache(baseUrl, requestParams)) == null) {
192             CLog.i("Attempting to connect to business logic service...");
193         }
194         long start = System.currentTimeMillis();
195         while (businessLogicString == null
196                 && System.currentTimeMillis() < (start + (mMaxConnectionTime * 1000))) {
197             try {
198                 businessLogicString = doPost(baseUrl, requestParams);
199             } catch (IOException e) {
200                 // ignore, re-attempt connection with remaining time
201                 CLog.d("BusinessLogic connection failure message: %s\nRetrying...", e.getMessage());
202                 RunUtil.getDefault().sleep(SLEEP_BETWEEN_CONNECTIONS_MS);
203             }
204         }
205         if (businessLogicString == null) {
206             if (mIgnoreFailure) {
207                 CLog.e("Failed to connect to business logic service.\nProceeding with test "
208                         + "invocation, tests depending on the remote configuration will fail.\n");
209                 return;
210             } else {
211                 throw new TargetSetupError(String.format("Cannot connect to business logic "
212                         + "service for config %s.\nIf this problem persists, re-invoking with "
213                         + "option '--ignore-business-logic-failure' will cause tests to execute "
214                         + "anyways (though tests depending on the remote configuration will fail).",
215                         mModuleName), device.getDeviceDescriptor());
216             }
217         }
218 
219         if (shouldWriteCache()) {
220             writeToCache(businessLogicString, baseUrl, requestParams, mCleanCache);
221         }
222         // Push business logic string to host file
223         try {
224             File hostFile = FileUtil.createTempFile(FILE_LOCATION, FILE_EXT);
225             FileUtil.writeToFile(businessLogicString, hostFile);
226             mHostFilePushed = hostFile.getAbsolutePath();
227             // Ensure bitness is set.
228             String bitness = (mAbi != null) ? mAbi.getBitness() : "";
229             buildHelper.setBusinessLogicHostFile(hostFile, bitness + mModuleName);
230         } catch (IOException e) {
231             throw new TargetSetupError(String.format(
232                     "Retrieved business logic for config %s could not be written to host",
233                     mModuleName), device.getDeviceDescriptor());
234         }
235         // Push business logic string to device file
236         removeDeviceFile(device); // remove any existing business logic file from device
237         if (device.pushString(businessLogicString, BusinessLogic.DEVICE_FILE)) {
238             mDeviceFilePushed = BusinessLogic.DEVICE_FILE;
239         } else {
240             throw new TargetSetupError(String.format(
241                     "Retrieved business logic for config %s could not be written to device %s",
242                     mModuleName, device.getSerialNumber()), device.getDeviceDescriptor());
243         }
244     }
245 
246     /** Helper to populate the business logic service request with info about the device. */
247     @VisibleForTesting
buildRequestParams(ITestDevice device, IBuildInfo buildInfo)248     String buildRequestParams(ITestDevice device, IBuildInfo buildInfo)
249             throws DeviceNotAvailableException {
250         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
251         MultiMap<String, String> paramMap = new MultiMap<>();
252         String suiteVersion = buildHelper.getSuiteVersion();
253         if (suiteVersion == null) {
254             suiteVersion = "null";
255         }
256         paramMap.put("suite_version", suiteVersion);
257         paramMap.put("module_version", mModuleVersion);
258         paramMap.put("oem", String.valueOf(PropertyUtil.getManufacturer(device)));
259         for (String feature : getBusinessLogicFeatures(device, buildInfo)) {
260             paramMap.put("features", feature);
261         }
262         for (String property : getBusinessLogicProperties(device, buildInfo)) {
263             paramMap.put("properties", property);
264         }
265         for (String pkg : getBusinessLogicPackages(device, buildInfo)) {
266             paramMap.put("packages", pkg);
267         }
268         for (String deviceInfo : getExtendedDeviceInfo(buildInfo)) {
269             paramMap.put("device_info", deviceInfo);
270         }
271         IHttpHelper helper = new HttpHelper();
272         String paramString = helper.buildParameters(paramMap);
273         CLog.d("Built param string: \"%s\"", paramString);
274         return paramString;
275     }
276 
277     /**
278      * Return the the first element of test-suite-tag from configuration if it's not empty,
279      * otherwise, return the name from test-suite-info.properties.
280      */
281     @VisibleForTesting
getSuiteName()282     String getSuiteName() {
283         String suiteName = null;
284         if (mModuleContext == null) {
285             suiteName = TestSuiteInfo.getInstance().getName().toLowerCase();
286         } else {
287             List<String> testSuiteTags = mModuleContext.getConfigurationDescriptor().
288                     getSuiteTags();
289             if (!testSuiteTags.isEmpty()) {
290                 if (testSuiteTags.size() >= 2) {
291                     CLog.i("More than 2 test-suite-tag are defined. test-suite-tag: " +
292                         testSuiteTags);
293                 }
294                 suiteName = testSuiteTags.get(0).toLowerCase();
295                 CLog.i("Using %s from test suite tags to get value from dynamic config", suiteName);
296             } else {
297                 suiteName = TestSuiteInfo.getInstance().getName().toLowerCase();
298                 CLog.i("Using %s from TestSuiteInfo to get value from dynamic config", suiteName);
299             }
300         }
301         return suiteName;
302     }
303 
304     /* Get device properties list, with element format "<property_name>:<property_value>" */
getBusinessLogicProperties(ITestDevice device, IBuildInfo buildInfo)305     private List<String> getBusinessLogicProperties(ITestDevice device, IBuildInfo buildInfo)
306             throws DeviceNotAvailableException {
307         List<String> properties = new ArrayList<>();
308         Map<String, String> clientIds = PropertyUtil.getClientIds(device);
309         for (Map.Entry<String, String> id : clientIds.entrySet()) {
310             // add client IDs to the list of properties
311             properties.add(String.format("%s:%s", id.getKey(), id.getValue()));
312         }
313 
314         try {
315             List<String> propertyNames = DynamicConfigFileReader.getValuesFromConfig(buildInfo,
316                     getSuiteName(), DYNAMIC_CONFIG_PROPERTIES_KEY);
317             for (String name : propertyNames) {
318                 // Use String.valueOf in case property is undefined for the device ("null")
319                 String value = String.valueOf(device.getProperty(name));
320                 properties.add(String.format("%s:%s", name, value));
321             }
322         } catch (XmlPullParserException | IOException e) {
323             CLog.e("Failed to pull business logic properties from dynamic config");
324         }
325         return properties;
326     }
327 
328     /* Get device features list */
getBusinessLogicFeatures(ITestDevice device, IBuildInfo buildInfo)329     private List<String> getBusinessLogicFeatures(ITestDevice device, IBuildInfo buildInfo)
330             throws DeviceNotAvailableException {
331         try {
332             List<String> dynamicConfigFeatures = DynamicConfigFileReader.getValuesFromConfig(
333                     buildInfo, getSuiteName(), DYNAMIC_CONFIG_FEATURES_KEY);
334             Set<String> deviceFeatures = FeatureUtil.getAllFeatures(device);
335             dynamicConfigFeatures.retainAll(deviceFeatures);
336             return dynamicConfigFeatures;
337         } catch (XmlPullParserException | IOException e) {
338             CLog.e("Failed to pull business logic features from dynamic config");
339             return new ArrayList<>();
340         }
341     }
342 
343     /* Get device packages list */
getBusinessLogicPackages(ITestDevice device, IBuildInfo buildInfo)344     private List<String> getBusinessLogicPackages(ITestDevice device, IBuildInfo buildInfo)
345             throws DeviceNotAvailableException {
346         try {
347             List<String> dynamicConfigPackages = DynamicConfigFileReader.getValuesFromConfig(
348                     buildInfo, getSuiteName(), DYNAMIC_CONFIG_PACKAGES_KEY);
349             Set<String> devicePackages = device.getInstalledPackageNames();
350             dynamicConfigPackages.retainAll(devicePackages);
351             return dynamicConfigPackages;
352         } catch (XmlPullParserException | IOException e) {
353             CLog.e("Failed to pull business logic packages from dynamic config");
354             return new ArrayList<>();
355         }
356     }
357 
358     /* Get extended device info*/
getExtendedDeviceInfo(IBuildInfo buildInfo)359     private List<String> getExtendedDeviceInfo(IBuildInfo buildInfo) {
360         List<String> extendedDeviceInfo = new ArrayList<>();
361         File deviceInfoPath = buildInfo.getFile(DeviceInfoCollector.DEVICE_INFO_DIR);
362         if (deviceInfoPath == null || !deviceInfoPath.exists()) {
363             CLog.w("Device Info directory was not created (Make sure you are not running plan " +
364                     "\"*ts-dev\" or including option -d/--skip-device-info)");
365             return extendedDeviceInfo;
366         }
367         List<String> requiredDeviceInfo = null;
368         try {
369             requiredDeviceInfo = DynamicConfigFileReader.getValuesFromConfig(
370                 buildInfo, getSuiteName(), DYNAMIC_CONFIG_EXTENDED_DEVICE_INFO_KEY);
371         } catch (XmlPullParserException | IOException e) {
372             CLog.e("Failed to pull business logic Extended DeviceInfo from dynamic config. "
373                 + "Error: %s", e);
374             return extendedDeviceInfo;
375         }
376         File ediFile = null;
377         String[] fileAndKey = null;
378         try{
379             for (String ediEntry: requiredDeviceInfo) {
380                 fileAndKey = ediEntry.split(":");
381                 if (fileAndKey.length <= 1) {
382                     CLog.e("Dynamic config Extended DeviceInfo key has problem.");
383                     return new ArrayList<>();
384                 }
385                 ediFile = FileUtil
386                     .findFile(deviceInfoPath, fileAndKey[0] + ".deviceinfo.json");
387                 if (ediFile == null) {
388                     CLog.e(
389                             "Could not find Extended DeviceInfo JSON file: %s.",
390                             deviceInfoPath + fileAndKey[0] + ".deviceinfo.json");
391                     return new ArrayList<>();
392                 }
393                 String jsonString = FileUtil.readStringFromFile(ediFile);
394                 JSONObject jsonObj = new JSONObject(jsonString);
395                 String value = jsonObj.getString(fileAndKey[1]);
396                 extendedDeviceInfo
397                     .add(String.format("%s:%s:%s", fileAndKey[0], fileAndKey[1], value));
398             }
399         }catch(JSONException | IOException | RuntimeException e){
400             CLog.e(
401                     "Failed to read or parse Extended DeviceInfo JSON file: %s. Error: %s",
402                     deviceInfoPath + fileAndKey[0] + ".deviceinfo.json", e);
403             return new ArrayList<>();
404         }
405         return extendedDeviceInfo;
406     }
407 
shouldReadCache()408     private boolean shouldReadCache() {
409         return mCache && !mCleanCache;
410     }
411 
shouldWriteCache()412     private boolean shouldWriteCache() {
413         return mCache || mCleanCache;
414     }
415 
416     /**
417      * Read the string from the business logic cache, handling the following cases with a null
418      * return value:
419      * - The cached file does not exist
420      * - The cached file cannot be read
421      * - The cached file is timestamped more than BL_CACHE_DAYS prior to now
422      * In the last two cases, the file is deleted so an up-to-date configuration may be cached anew
423      */
readFromCache(String baseUrl, String params)424     private static synchronized String readFromCache(String baseUrl, String params) {
425         // baseUrl + params hashCode makes file unique, in case host runs invocations for different
426         // device builds and/or test suites using business logic
427         File cachedFile = getCachedFile(baseUrl, params);
428         if (!cachedFile.exists()) {
429             CLog.i("No cached business logic found");
430             return null;
431         }
432         try {
433             BusinessLogic cachedLogic = BusinessLogicFactory.createFromFile(cachedFile);
434             Date cachedDate = cachedLogic.getTimestamp();
435             if (System.currentTimeMillis() - cachedDate.getTime() < BL_CACHE_MILLIS) {
436                 CLog.i("Using cached business logic from: %s", cachedDate.toString());
437                 return FileUtil.readStringFromFile(cachedFile);
438             } else {
439                 CLog.i("Cached business logic out-of-date, deleting cached file");
440                 FileUtil.deleteFile(cachedFile);
441             }
442         } catch (IOException e) {
443             CLog.w("Failed to read cached business logic, deleting cached file");
444             FileUtil.deleteFile(cachedFile);
445         }
446         return null;
447     }
448 
449     /**
450      * Write a string retrieved from the business logic service to the cache file, only if the
451      * file does not already exist. Synchronize this method to prevent concurrent writes in the
452      * sharding case.
453      * @param blString the string to cache
454      * @param baseUrl the base business logic request url containing suite info
455      * @param params the string of params for the business logic request containing device info
456      */
writeToCache(String blString, String baseUrl, String params, boolean overwrite)457     private static synchronized void writeToCache(String blString, String baseUrl, String params,
458             boolean overwrite) {
459         // baseUrl + params hashCode makes file unique, in case host runs invocations for different
460         // device builds and/or test suites using business logic
461         File cachedFile = getCachedFile(baseUrl, params);
462         if (!cachedFile.exists() || overwrite) {
463             // don't overwrite existing file, whether from previous shard or previous invocation
464             try {
465                 FileUtil.writeToFile(blString, cachedFile);
466             } catch (IOException e) {
467                 throw new RuntimeException("Failed to write business logic to cache file", e);
468             }
469         }
470     }
471 
472     /**
473      * Get the cached business logic file given the base url and params used to retrieve this logic.
474      */
getCachedFile(String baseUrl, String params)475     private static File getCachedFile(String baseUrl, String params) {
476         int hashCode = (baseUrl + params).hashCode();
477         return new File(System.getProperty("java.io.tmpdir"), BL_CACHE_FILE + hashCode);
478     }
479 
doPost(String baseUrl, String params)480     private String doPost(String baseUrl, String params) throws IOException {
481         String accessToken = getToken();
482         if (Strings.isNullOrEmpty(accessToken)) {
483             // Set API key on base URL
484             baseUrl += String.format("?key=%s", mApiKey);
485         }
486         URL url = new URL(baseUrl);
487         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
488         conn.setRequestMethod("POST");
489         conn.setRequestProperty("User-Agent", "BusinessLogicClient");
490         if (!Strings.isNullOrEmpty(accessToken)) {
491             // Set authorization access token in POST header
492             conn.setRequestProperty("Authorization", String.format("Bearer %s", accessToken));
493         }
494         // Send params in POST request body
495         conn.setDoOutput(true);
496         try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) {
497             wr.writeBytes(params);
498         }
499         int responseCode = conn.getResponseCode();
500         CLog.d("Business Logic Service Response Code : %s", responseCode);
501         return StreamUtil.getStringFromStream(conn.getInputStream());
502     }
503 
504     /** {@inheritDoc} */
505     @Override
tearDown(TestInformation testInfo, Throwable e)506     public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
507         // Clean up existing host and device files unconditionally
508         if (mHostFilePushed != null) {
509             FileUtil.deleteFile(new File(mHostFilePushed));
510         }
511         if (mDeviceFilePushed != null && !(e instanceof DeviceNotAvailableException)) {
512             removeDeviceFile(testInfo.getDevice());
513         }
514     }
515 
516     /** Remove business logic file from the device */
removeDeviceFile(ITestDevice device)517     private static void removeDeviceFile(ITestDevice device) throws DeviceNotAvailableException {
518         device.deleteFile(BusinessLogic.DEVICE_FILE);
519     }
520 
521     /**
522      * Returns an OAuth2 token string obtained using a service account json key file.
523      *
524      * Uses the service account key file location stored in environment variable 'APE_API_KEY'
525      * to request an OAuth2 token. If APE_API_KEY wasn't set, try to get if file is dynamically
526      * downloaded from GlobalConfiguration.
527      */
getToken()528     private String getToken() {
529         String keyFilePath = System.getenv("APE_API_KEY");
530         if (Strings.isNullOrEmpty(keyFilePath)) {
531             File globalKeyFile = GlobalConfiguration.getInstance().getHostOptions().
532                 getServiceAccountJsonKeyFiles().get(GLOBAL_APE_API_KEY);
533             if (globalKeyFile == null || !globalKeyFile.exists()) {
534                 CLog.d("Unable to fetch the service key because neither environment variable " +
535                         "APE_API_KEY is set nor the key file is dynamically downloaded.");
536                 return null;
537             }
538             keyFilePath = globalKeyFile.getAbsolutePath();
539         }
540         if (Strings.isNullOrEmpty(mApiScope)) {
541             CLog.d("API scope not set, use flag --business-logic-api-scope.");
542             return null;
543         }
544         try {
545             Credential credential = GoogleCredential.fromStream(new FileInputStream(keyFilePath))
546                     .createScoped(Collections.singleton(mApiScope));
547             credential.refreshToken();
548             return credential.getAccessToken();
549         } catch (FileNotFoundException e) {
550             CLog.e(String.format("Service key file %s doesn't exist.", keyFilePath));
551         } catch (IOException e) {
552             CLog.e(String.format("Can't read the service key file, %s", keyFilePath));
553         }
554         return null;
555     }
556 }
557