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