1 /* 2 * Copyright (C) 2018 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.config.gcs; 18 19 import com.android.tradefed.config.ConfigurationDef; 20 import com.android.tradefed.config.ConfigurationException; 21 import com.android.tradefed.config.ConfigurationFactory; 22 import com.android.tradefed.config.IConfigurationFactory; 23 import com.android.tradefed.config.IConfigurationServer; 24 import com.android.tradefed.util.FileUtil; 25 26 import java.io.BufferedInputStream; 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.nio.file.Path; 31 import java.nio.file.Paths; 32 import java.util.Map; 33 34 /** A {@link ConfigurationFactory} loads configs from Google Cloud Storage. */ 35 public class GCSConfigurationFactory extends ConfigurationFactory { 36 private static IConfigurationFactory sInstance = null; 37 private IConfigurationServer mConfigServer; 38 /** Keep track of the latest downloaded config file so we can use it */ 39 private File mConfigDownloaded = null; 40 GCSConfigurationFactory(IConfigurationServer configServer)41 GCSConfigurationFactory(IConfigurationServer configServer) { 42 mConfigServer = configServer; 43 } 44 45 /** Get the singleton {@link IConfigurationFactory} instance. */ getInstance(IConfigurationServer configServer)46 public static IConfigurationFactory getInstance(IConfigurationServer configServer) { 47 if (sInstance == null) { 48 sInstance = new GCSConfigurationFactory(configServer); 49 } 50 return sInstance; 51 } 52 53 /** 54 * Loads an InputStream for given config name from Google Cloud Storage(GCS). 55 * 56 * @param name the configuration name to load 57 * @return a {@link BufferedInputStream} for reading config contents 58 * @throws ConfigurationException if config could not be found 59 */ 60 @Override getConfigStream(String name)61 protected BufferedInputStream getConfigStream(String name) throws ConfigurationException { 62 InputStream configStream = getBundledConfigStream(name); 63 if (configStream == null) { 64 // now try to load from GCS 65 configStream = mConfigServer.getConfig(name); 66 if (configStream == null) { 67 throw new ConfigurationException( 68 String.format("Could not find configuration '%s'", name)); 69 } 70 // Create a local copy of the configuration 71 try { 72 // If available, store the downloaded global config within the Tradefed directory 73 File tfDir = null; 74 String tfPath = System.getProperty("TF_JAR_DIR"); 75 if (tfPath != null) { 76 // In some cases, a '.' for current is given, handle it. 77 if (tfPath.equals(".")) { 78 tfDir = new File("").getAbsoluteFile(); 79 } else { 80 tfDir = new File(tfPath).getAbsoluteFile(); 81 } 82 } 83 mConfigDownloaded = 84 FileUtil.createTempFile("gcs-downloaded-global-config", ".xml", tfDir); 85 mConfigDownloaded.deleteOnExit(); 86 FileUtil.writeToFile(configStream, mConfigDownloaded); 87 // Reset the stream to be available from the start again. 88 configStream.reset(); 89 } catch (IOException e) { 90 throw new ConfigurationException(e.getMessage()); 91 } 92 } 93 // buffer input for performance - just in case config file is large 94 return new BufferedInputStream(configStream); 95 } 96 getLatestDownloadedFile()97 public File getLatestDownloadedFile() { 98 return mConfigDownloaded; 99 } 100 101 /** {@inheritDoc} */ 102 @Override getConfigurationDef( String name, boolean isGlobal, Map<String, String> templateMap)103 protected ConfigurationDef getConfigurationDef( 104 String name, boolean isGlobal, Map<String, String> templateMap) 105 throws ConfigurationException { 106 return new GCSConfigLoader(isGlobal).getConfigurationDef(name, templateMap); 107 } 108 109 /** 110 * Extension of {@link com.android.tradefed.config.ConfigurationFactory.ConfigLoader} that loads 111 * config from GCS, tracks the included configurations from one root config, and throws an 112 * exception on circular includes. 113 */ 114 protected class GCSConfigLoader extends ConfigLoader { 115 GCSConfigLoader(boolean isGlobalConfig)116 public GCSConfigLoader(boolean isGlobalConfig) { 117 super(isGlobalConfig); 118 } 119 120 @Override findConfigName(String name, String parentName)121 protected String findConfigName(String name, String parentName) 122 throws ConfigurationException { 123 if (isBundledConfig(name)) { 124 return name; 125 } 126 if (parentName == null || isBundledConfig(parentName)) { 127 return name; 128 } 129 130 return getPath(parentName, name); 131 } 132 133 /** 134 * Help method to get file's path from its parent and filename. This is mainly for normalize 135 * path like path/to/../file to path/file 136 * 137 * @param parent parent name 138 * @param filename file name 139 * @return normalized path 140 */ getPath(String parent, String filename)141 String getPath(String parent, String filename) { 142 Path parentPath = Paths.get(parent).getParent(); 143 if (parentPath == null) { 144 return filename; 145 } 146 return parentPath.resolve(filename).normalize().toString(); 147 } 148 149 /** {@inheritDoc} */ 150 @Override trackConfig(String name, ConfigurationDef def)151 protected void trackConfig(String name, ConfigurationDef def) { 152 // FIXME: Track remote config source files' life cycle. 153 return; 154 } 155 } 156 } 157