Rewrite local connector to eliminate static direct API calls for

configuration.
This commit is contained in:
Christopher Smith
2014-07-16 19:48:27 -05:00
parent 2fa85b99f5
commit a865a3ca7d
19 changed files with 479 additions and 229 deletions

View File

@@ -3,30 +3,39 @@ package org.springframework.cloud.util;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Properties;
import org.springframework.cloud.CloudConnector;
import org.springframework.cloud.CloudException;
/**
* Environment available to the deployed app.
*
*
* The main purpose of this class is to allow unit-testing of {@link CloudConnector} implementations
* that rely on environment
*
*
* @author Ramnivas Laddad
*/
public class EnvironmentAccessor {
public Map<String, String> getEnv() {
return System.getenv();
}
public String getEnvValue(String key) {
return System.getenv(key);
}
public String getPropertyValue(String key) {
return System.getProperty(key);
public Properties getSystemProperties() {
return System.getProperties();
}
public String getSystemProperty(String key) {
return getSystemProperty(key, null);
}
public String getSystemProperty(String key, String def) {
return System.getProperty(key, def);
}
public String getHost() {

View File

@@ -1,29 +1,36 @@
#Spring Cloud local-configuration connector
This connector provides the ability to configure Spring Cloud services locally for development
or testing. The current implementation reads from Java properties only; in order to prevent
dependencies on the Spring Framework, the placeholder functionality is unavailable in the
connector. Pull requests for also inspecting environment variables are welcome.
or testing. The current implementation reads from Java properties only. Pull requests for also
inspecting environment variables are welcome.
##Quick start
Since service URIs contain passwords and should not be stored in code, this connector does not
attempt to read properties out of the classpath. You can provide a filename with service definitions
by setting the `spring.cloud.propertiesFile` system property:
attempt to read service definitions out of the classpath. You can provide service definitions
as system properties
````
java -Dspring.cloud.database='mysql://user:pass@host:1234/dbname' -jar my-app.jar
````
and from a configuration properties file either by setting the `spring.cloud.propertiesFile` system property
````
java -Dspring.cloud.propertiesFile=/path/to/spring-cloud.properties -jar my-app.jar
````
or by passing in an open `InputStream`:
or by providing a *bootstrap* properties file on the runtime classpath named
`spring-cloud-bootstrap.properties`. This file will be inspected for only
the property named `spring.cloud.propertiesFile`, and its value will be interpolated
from the system properties.
````java
InputStream propertyStream = new FileInputStream("/path/to/spring-cloud.properties");
LocalConfigConnector.supplyProperties(propertyStream);
Cloud cloud = new CloudFactory().getCloud();
````properties
spring.cloud.propertiesFile: ${user.home}/.config/myApp/spring-cloud.properties
````
The property file should contain an application ID and the desired services in this format:
The system properties or the configuration properties file should contain an application ID
and the desired services in this format:
````properties
spring.cloud.appId: myApp
@@ -32,8 +39,7 @@ spring.cloud.database: mysql://user:pass@host:1234/dbname
````
Service type is determined by the URI scheme. The connector will activate if it finds a property
(in the system properties, supplied properties, or the file provided in `spring.cloud.propertiesFile`)
named `spring.cloud.appId`.
(in the system properties or the configuration properties file) named `spring.cloud.appId`.
##Property sources
@@ -43,31 +49,33 @@ This connector first attempts to read the system properties generally and a syst
If a system property named `spring.cloud.propertiesFile` is found, that file will be loaded
as a property list.
###Programmatically supplying properties
You can programmatically supply a property source by calling the static method
`LocalConfigConnector.supplyProperties(InputStream)` before invoking `getCloud()`.
Calling this method will cause the connector to read the stream as a property list
and then close the stream. Calling this method after invoking `getCloud()` will
still read the stream, but the properties will have no effect on the connector
service configuration. Calling this method multiple times will load the supplied
streams onto the same `Properties` object, overwriting duplicates.
###Providing a bootstrap properties file
To avoid having to manually configure run configurations or test runners with the path to the
configuration properties file, the connector supports reading a templated filename out of the
runtime classpath. This file must be named `spring-cloud-bootstrap.properties` and located
at the classpath root, and for security the connector will not attempt to read any service URIs
out of it. If the connector does find the file, it will read the property
`spring.cloud.propertiesFile` and [substitute the pattern
`${system.property}`](http://commons.apache.org/proper/commons-lang/javadocs/api-release/index.html?org/apache/commons/lang3/text/StrSubstitutor.html)
with the appropriate value from the system properties. The most useful option is generally
`${user.home}`.
A configuration properties file specified in the system properties will override any bootstrap
file that may be available on the classpath.
###Property precedence
To provide the maximum configuration flexibility, the connector will scan the available
property sources in this order:
- programmatically-supplied properties
- properties read from `spring.cloud.propertiesFile`
- system properties
The last definition of a specific service ID wins. The connector will log a message at
To provide the maximum configuration flexibility, the connector will override any properties
(both application ID and service definitions) specified in the file at `spring.cloud.propertiesFile`
with system properties defined at runtime. The connector will log a message at
`WARN` if you override a service ID.
##Activating the connector
The Spring Cloud core expects exactly one cloud connector match the runtime environment.
This connector identifies the "local cloud" by the presence of a property named
`spring.cloud.appId`, which will be used in the `ApplicationInstanceInfo`.
`spring.cloud.appId` in a configuration properties file or the system properties,
which will be used in the `ApplicationInstanceInfo`.
##Service definitions

View File

@@ -2,5 +2,5 @@ description = 'Spring Cloud local-configuration connector'
dependencies {
compile project(':spring-cloud-core')
testCompile 'com.github.stefanbirkner:system-rules:1.5.0'
compile 'org.apache.commons:commons-lang3:3.3.2'
}

View File

@@ -1,5 +1,6 @@
package org.springframework.cloud.localconfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -20,6 +21,7 @@ import org.springframework.cloud.app.BasicApplicationInstanceInfo;
import org.springframework.cloud.service.BaseServiceInfo;
import org.springframework.cloud.service.FallbackBaseServiceInfoCreator;
import org.springframework.cloud.service.UriBasedServiceData;
import org.springframework.cloud.util.EnvironmentAccessor;
/**
*
@@ -46,11 +48,13 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
public static final List<String> META_PROPERTIES = Collections.unmodifiableList(
Arrays.asList(new String[] { APP_ID_PROPERTY, PROPERTIES_FILE_PROPERTY }));
/*--------------- sources for service-definition properties ---------------*/
/*--------------- inject system property access for testing ---------------*/
static Properties programmaticProperties = new Properties();
private EnvironmentAccessor env = new EnvironmentAccessor();
private Properties fileProperties = null;
void setEnvironmentAccessor(EnvironmentAccessor env) {
this.env = env;
}
/*--------------- API implementation ---------------*/
@@ -59,6 +63,10 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
super((Class) LocalConfigServiceInfoCreator.class);
}
/*--------------- properties read out of the file at spring.cloud.propertiesFile ---------------*/
private Properties fileProperties = null;
/**
* Returns {@code true} if a property named {@code spring.cloud.appId} is present in any of the property sources.
* On the first call, attempts to load properties from a file specified in {@code spring.cloud.propertiesFile}.
@@ -79,15 +87,14 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
@Override
protected List<UriBasedServiceData> getServicesData() {
if(fileProperties == null)
if (fileProperties == null)
throw new IllegalStateException("isInMatchingCloud() must be called first to initialize connector");
LinkedHashMap<String, Properties> propertySources = new LinkedHashMap<String, Properties>();
propertySources.put("programmatic properties", programmaticProperties);
propertySources.put("properties from file", fileProperties);
try {
propertySources.put("system properties", System.getProperties());
propertySources.put("system properties", env.getSystemProperties());
} catch (SecurityException e) {
logger.log(Level.WARNING,
"couldn't read system properties; no service definitions from system properties will be applied", e);
@@ -103,20 +110,6 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
/*--------------- methods for manipulating properties and sources ---------------*/
/**
* Adds properties to be scanned from the supplied {@link InputStream}, overwriting
* existing properties with the same name. Closes the stream after loading.
*
* @param propertiesInputStream
* a property list
* @throws IOException
* if the underlying load operation throws an exception
*/
public static void supplyProperties(final InputStream propertiesInputStream) throws IOException {
programmaticProperties.load(propertiesInputStream);
propertiesInputStream.close();
}
/**
* Checks for the presence of a supplied or system property named {@code spring.cloud.propertiesFile}. If the property
* is present, load its contents into {@link #fileProperties}. If there's a problem, log but continue.
@@ -125,29 +118,22 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
fileProperties = new Properties();
logger.fine("looking for a properties file");
String filename = null;
// will search system properties and the classpath
File propertiesFile = new PropertiesFileResolver(env).findCloudPropertiesFile();
filename = programmaticProperties.getProperty(PROPERTIES_FILE_PROPERTY);
try {
filename = System.getProperty(PROPERTIES_FILE_PROPERTY, filename);
} catch (SecurityException e) {
logSystemReadException(PROPERTIES_FILE_PROPERTY, e);
if (propertiesFile == null) {
logger.info("not loading service definitions from a properties file");
return;
}
if (filename == null) {
logger.info("did not find a system property " + PROPERTIES_FILE_PROPERTY);
return;
}
logger.info("loading properties from file " + filename);
logger.info("loading service definitions from properties file " + propertiesFile);
try {
InputStream fis = openFile(filename);
InputStream fis = openFile(propertiesFile);
fileProperties.load(fis);
fis.close();
} catch (IOException e) {
logger.log(Level.SEVERE, "exception while loading properties from file " + filename, e);
logger.log(Level.SEVERE, "exception while loading properties from file " + propertiesFile, e);
return;
}
@@ -156,27 +142,29 @@ public class LocalConfigConnector extends AbstractCloudConnector<UriBasedService
/**
* Broken out into a separate method for mocking the filesystem.
* @param filename the file to open
*
* @param filename
* the file to open
* @return a {@code FileInputStream} to the file
* @throws IOException if opening the file throws
* @throws IOException
* if opening the file throws
*/
InputStream openFile(String filename) throws IOException {
return new FileInputStream(filename);
InputStream openFile(File file) throws IOException {
return new FileInputStream(file);
}
/**
* Look for a specific property in programmatically-supplied properties, properties from a file,
* or the system properties. Last source wins.
* Look for a specific property in the config file or the system properties.
*
* @param key
* the property to look for
* @return the highest-priority value for the key, or {@code null} if the key is not found
* @return the preferred value for the key, or {@code null} if the key is not found
*/
private String findProperty(String key) {
String value = programmaticProperties.getProperty(key);
value = fileProperties.getProperty(key, value);
String value = fileProperties.getProperty(key);
try {
value = System.getProperty(key, value);
value = env.getSystemProperty(key, value);
} catch (SecurityException e) {
logSystemReadException(key, e);
}

View File

@@ -0,0 +1,150 @@
package org.springframework.cloud.localconfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.springframework.cloud.util.EnvironmentAccessor;
/**
* Helper class that handles finding and merging properties to be read by the connector.
*
* @author Christopher Smith
*
*/
class PropertiesFileResolver {
public static final String BOOTSTRAP_PROPERTIES_FILENAME = "spring-cloud-bootstrap.properties";
private static final Logger logger = Logger.getLogger(PropertiesFileResolver.class.getName());
private final EnvironmentAccessor env;
private final String classpathPropertiesFilename;
PropertiesFileResolver(final EnvironmentAccessor env, final String classpathPropertiesFilename) {
this.env = env;
this.classpathPropertiesFilename = classpathPropertiesFilename;
}
PropertiesFileResolver(final EnvironmentAccessor env) {
this(env, BOOTSTRAP_PROPERTIES_FILENAME);
}
PropertiesFileResolver() {
this(new EnvironmentAccessor());
}
/**
* Inspects the system properties for an entry named {@value LocalConfigConnector#PROPERTIES_FILE_PROPERTY} directing it to an
* external properties file.
*
* @return a {@code File} pointing to the external properties file, or {@code null} if the system property couldn't be read
*/
File findCloudPropertiesFileFromSystem() {
String filename = null;
try {
filename = env.getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
} catch (SecurityException e) {
logger.log(Level.WARNING, "SecurityManager prevented reading system property "
+ LocalConfigConnector.PROPERTIES_FILE_PROPERTY, e);
return null;
}
if (filename == null) {
logger.fine("didn't find system property for a configuration file");
return null;
}
File file = new File(filename);
logger.info("found system property for a configuration file: " + file);
return file;
}
/**
* Looks for a resource named {@code filename} (usually {@value #BOOTSTRAP_PROPERTIES_FILENAME}) on the classpath. If present,
* it is loaded and
* inspected for a property named {@value LocalConfigConnector#PROPERTIES_FILE_PROPERTY}, which is interpolated from the system
* properties and returned.
*
* @return the filename derived from the classpath control file, or {@code null} if one couldn't be found
*/
File findCloudPropertiesFileFromClasspath() {
// see if we have a spring-cloud.properties at all
InputStream in = getClass().getClassLoader().getResourceAsStream(classpathPropertiesFilename);
if (in == null) {
logger.info("no " + classpathPropertiesFilename
+ " found on the classpath to direct us to an external properties file");
return null;
}
// load it as a properties file
Properties properties = new Properties();
try {
properties.load(in);
} catch (IOException e) {
logger.log(Level.SEVERE, "found " + classpathPropertiesFilename
+ " on the classpath but couldn't load it as a properties file", e);
return null;
}
// read the spring.cloud.propertiesFile property from it
String template = properties.getProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
if (template == null) {
logger.log(Level.SEVERE, "found properties file " + classpathPropertiesFilename
+ " on the classpath, but it didn't contain a property named " + LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
return null;
}
// if there's anything else, the client probably tried to put an app ID or other credentials there
if (properties.entrySet().size() > 1)
logger.warning("the properties file " + classpathPropertiesFilename + " contained properties besides "
+ LocalConfigConnector.PROPERTIES_FILE_PROPERTY + "; ignoring");
logger.fine("substituting system properties into '" + template + "'");
File configFile = new File(new StrSubstitutor(systemPropertiesLookup(env)).replace(template));
logger.info("derived configuration file name: " + configFile);
return configFile;
}
File findCloudPropertiesFile() {
File file = findCloudPropertiesFileFromSystem();
if (file != null) {
logger.info("using configuration file from system properties");
return file;
}
file = findCloudPropertiesFileFromClasspath();
if (file != null)
logger.info("using configuration file derived from " + classpathPropertiesFilename);
else
logger.info("did not find any Spring Cloud configuration file");
return file;
}
/**
* Adapter from the {@link EnvironmentAccessor}'s system-property resolution to the {@code StrLookup} interface.
*
* @param env
* the {@code EnvironmentAccessor} to use for the lookups
* @return a {@code StrLookup} view of the accessor's system properties
*/
private StrLookup<String> systemPropertiesLookup(final EnvironmentAccessor env) {
return new StrLookup<String>() {
@Override
public String lookup(String key) {
return env.getSystemProperty(key);
}
};
}
}

View File

@@ -1,14 +1,10 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import org.junit.After;
import org.junit.Before;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.UriBasedServiceInfo;
@@ -17,7 +13,9 @@ public class AbstractLocalConfigConnectorTest {
public static final String PROPERTIES_FILE = "localconfig.testuris.properties";
protected LocalConfigConnector connector = new LocalConfigConnector();
protected StubbedOpenFileLocalConfigConnector connector = new StubbedOpenFileLocalConfigConnector();
protected PassthroughEnvironmentAccessor env;
protected static final String HOSTNAME = "10.20.30.40";
protected static final int PORT = 1234;
@@ -26,14 +24,9 @@ public class AbstractLocalConfigConnectorTest {
@Before
public void init() throws IOException {
InputStream propertiesFile = getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
LocalConfigConnector.supplyProperties(propertiesFile);
assertTrue(connector.isInMatchingCloud());
}
@After
public void clearProperties() {
LocalConfigConnector.programmaticProperties = new Properties();
env = new PassthroughEnvironmentAccessor();
env.setSystemProperty("spring.cloud.baz", "inline!");
connector.setEnvironmentAccessor(env);
}
protected static ServiceInfo getServiceInfo(List<ServiceInfo> serviceInfos, String serviceId) {

View File

@@ -0,0 +1,20 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertTrue;
import java.io.InputStream;
import org.junit.Before;
public class AbstractLocalConfigConnectorWithUrisTest extends AbstractLocalConfigConnectorTest {
public static String PROPERTY_FILE_WITH_URIS = "localconfig.testuris.properties";
@Before
public void useTestUris() {
InputStream testUrisProperties = getClass().getClassLoader().getResourceAsStream(PROPERTY_FILE_WITH_URIS);
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_WITH_URIS);
connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromStream(PROPERTY_FILE_WITH_URIS, testUrisProperties));
assertTrue(connector.isInMatchingCloud());
}
}

View File

@@ -9,7 +9,7 @@ import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.AmqpServiceInfo;
public class LocalConfigConnectorAmqpServiceTest extends AbstractLocalConfigConnectorTest {
public class LocalConfigConnectorAmqpServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
@Test
public void serviceCreation() {

View File

@@ -9,7 +9,7 @@ import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.MongoServiceInfo;
public class LocalConfigConnectorMongoServiceTest extends AbstractLocalConfigConnectorTest {
public class LocalConfigConnectorMongoServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
@Test
public void serviceCreation() {

View File

@@ -9,7 +9,7 @@ import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.MysqlServiceInfo;
public class LocalConfigConnectorMysqlServiceTest extends AbstractLocalConfigConnectorTest {
public class LocalConfigConnectorMysqlServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
@Test
public void serviceCreation() {

View File

@@ -9,7 +9,7 @@ import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.PostgresqlServiceInfo;
public class LocalConfigConnectorPostgresqlServiceTest extends AbstractLocalConfigConnectorTest {
public class LocalConfigConnectorPostgresqlServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
@Test
public void serviceCreation() {

View File

@@ -9,7 +9,7 @@ import org.junit.Test;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.RedisServiceInfo;
public class LocalConfigConnectorRedisServiceTest extends AbstractLocalConfigConnectorTest {
public class LocalConfigConnectorRedisServiceTest extends AbstractLocalConfigConnectorWithUrisTest {
@Test
public void serviceCreation() {

View File

@@ -4,142 +4,41 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Properties;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ClearSystemProperties;
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
import org.springframework.cloud.service.UriBasedServiceData;
public class LocalConfigConnectorTest {
public class LocalConfigConnectorTest extends AbstractLocalConfigConnectorTest {
static final Charset UTF_8 = Charset.forName("UTF-8");
public static final String APP_ID_1 = "appId1";
public static final String APP_ID_1_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID_1;
public static final String APP_ID_2 = "appId2";
public static final String APP_ID_2_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID_2;
public static final String APP_ID = "appId2";
public static final String APP_ID_PROPERTY = LocalConfigConnector.APP_ID_PROPERTY + ": " + APP_ID;
public static final String PROPERTY_FILE_NAME = "localconfig.nonsense.properties";
public static final String PROPERTY_FILE_PROPERTY = LocalConfigConnector.PROPERTIES_FILE_PROPERTY + ": " + PROPERTY_FILE_NAME;
public static class DetectAppIdTest {
private LocalConfigConnector connector;
@Before
public void setup() {
connector = new LocalConfigConnector();
}
@After
public void clearProperties() {
LocalConfigConnector.programmaticProperties = new Properties();
}
@Rule
public final ClearSystemProperties NO_APP_ID_PROPERTY = new ClearSystemProperties(LocalConfigConnector.APP_ID_PROPERTY);
@Test
public void testNoAppIdAnywhere() {
assertFalse(connector.isInMatchingCloud());
}
@Test
public void testProgrammaticAppId() throws IOException {
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(APP_ID_1_PROPERTY.getBytes(UTF_8)));
assertTrue(connector.isInMatchingCloud());
assertEquals(APP_ID_1, connector.getApplicationInstanceInfo().getAppId());
}
@Test
public void testProgrammaticAndFileAppIds() throws IOException {
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(APP_ID_1_PROPERTY.getBytes(UTF_8)));
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(PROPERTY_FILE_PROPERTY.getBytes(UTF_8)));
LocalConfigConnector stubConnector = new LocalConfigConnector() {
@Override
InputStream openFile(String filename) throws IOException {
assertEquals(PROPERTY_FILE_NAME, filename);
return new ByteArrayInputStream(APP_ID_2_PROPERTY.getBytes(UTF_8));
};
};
assertTrue(stubConnector.isInMatchingCloud());
assertEquals(APP_ID_2, stubConnector.getApplicationInstanceInfo().getAppId());
}
@Test
public void testProgrammaticFilenamePlusSystemAppId() throws IOException {
LocalConfigConnector.supplyProperties(new ByteArrayInputStream(PROPERTY_FILE_PROPERTY.getBytes(UTF_8)));
LocalConfigConnector stubConnector = new LocalConfigConnector() {
@Override
InputStream openFile(String filename) throws IOException {
assertEquals(PROPERTY_FILE_NAME, filename);
return new ByteArrayInputStream(APP_ID_2_PROPERTY.getBytes(UTF_8));
};
};
System.setProperty(LocalConfigConnector.APP_ID_PROPERTY, "helloApp");
assertTrue(stubConnector.isInMatchingCloud());
assertEquals("helloApp", stubConnector.getApplicationInstanceInfo().getAppId());
}
}
private LocalConfigConnector connector;
InputStream propertiesFile;
@Before
public void setup() {
connector = new LocalConfigConnector();
propertiesFile = LocalConfigConnectorTest.class.getClassLoader().getResourceAsStream(PROPERTY_FILE_NAME);
}
@After
public void cleanup() throws IOException {
LocalConfigConnector.programmaticProperties = new Properties();
propertiesFile.close();
@Test
public void testNoAppIdAnywhere() {
assertFalse(connector.isInMatchingCloud());
}
@Test
public void testLoadFromFile() throws IOException {
LocalConfigConnector.supplyProperties(propertiesFile);
public void testAppIdInConfigFile() throws IOException {
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_NAME);
connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromString(PROPERTY_FILE_NAME, APP_ID_PROPERTY));
assertTrue(connector.isInMatchingCloud());
assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId());
List<UriBasedServiceData> services = connector.getServicesData();
assertEquals(2, services.size());
for (UriBasedServiceData service : services)
if ("foo".equals(service.getKey()))
assertEquals("bar", service.getUri());
assertEquals(APP_ID, connector.getApplicationInstanceInfo().getAppId());
}
@Rule
public ProvideSystemProperty BAZ_PROPERTY = new ProvideSystemProperty("spring.cloud.baz", "inline!");
@Test
public void testLoadFromInputStreamWithOverride() throws IOException {
LocalConfigConnector.supplyProperties(propertiesFile);
public void testAppIdInFileAndSystem() throws IOException {
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTY_FILE_NAME);
env.setSystemProperty(LocalConfigConnector.APP_ID_PROPERTY, APP_ID);
connector.setFileProvider(StubbedOpenFileLocalConfigConnector.fileContentsFromString(PROPERTY_FILE_NAME, APP_ID_PROPERTY));
assertTrue(connector.isInMatchingCloud());
assertEquals("testApp", connector.getApplicationInstanceInfo().getAppId());
List<UriBasedServiceData> services = connector.getServicesData();
assertEquals(2, services.size());
for(UriBasedServiceData service: services)
if("baz".equals(service.getKey()))
assertEquals("inline!", service.getUri());
assertEquals(APP_ID, connector.getApplicationInstanceInfo().getAppId());
}
}

View File

@@ -6,22 +6,16 @@ import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.MongoServiceInfo;
public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnectorTest {
@Rule
public final ProvideSystemProperty OVERRIDE_MYSQL =
new ProvideSystemProperty(
"spring.cloud.candygram",
"mongodb://youruser:yourpass@40.30.20.10:4321/dbname");
public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnectorWithUrisTest {
@Test
public void serviceOverride() {
env.setSystemProperty("spring.cloud.candygram", "mongodb://youruser:yourpass@40.30.20.10:4321/dbname");
List<ServiceInfo> services = connector.getServiceInfos();
ServiceInfo service = getServiceInfo(services, "candygram");
assertNotNull(service);
@@ -30,5 +24,4 @@ public class LocalConfigServiceOverrideTest extends AbstractLocalConfigConnector
assertEquals("youruser", mongo.getUserName());
assertEquals(4321, mongo.getPort());
}
}

View File

@@ -0,0 +1,27 @@
package org.springframework.cloud.localconfig;
import java.util.Properties;
import org.springframework.cloud.util.EnvironmentAccessor;
class PassthroughEnvironmentAccessor extends EnvironmentAccessor {
private Properties systemProperties = new Properties(System.getProperties());
void clear() {
systemProperties.clear();
}
void setSystemProperty(String key, String value) {
systemProperties.setProperty(key, value);
}
@Override
public String getSystemProperty(String key, String defaultValue) {
return systemProperties.getProperty(key, defaultValue);
}
@Override
public Properties getSystemProperties() {
return systemProperties;
}
}

View File

@@ -0,0 +1,96 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
public class PropertiesFileResolverTest {
private PassthroughEnvironmentAccessor env;
private PropertiesFileResolver resolver;
private String PROPERTIES_FILE_NAME = "/foo/bar.properties";
@Before
public void setDefaults() {
env = new PassthroughEnvironmentAccessor();
resolver = new PropertiesFileResolver(env);
}
@Test
public void testSecurityExceptionHandling() {
env = mock(PassthroughEnvironmentAccessor.class);
resolver = new PropertiesFileResolver(env);
when(env.getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY)).thenThrow(new SecurityException());
assertNull(resolver.findCloudPropertiesFileFromSystem());
verify(env).getSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY);
}
@Test
public void testMissingSystemProperty() {
assertNull(resolver.findCloudPropertiesFileFromSystem());
}
@Test
public void testSystemProperty() {
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME);
assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFileFromSystem().getPath());
}
@Test
public void testNoClasspathFile() {
resolver = new PropertiesFileResolver(env, "bazquux.properties");
assertNull(resolver.findCloudPropertiesFileFromClasspath());
}
@Test
public void testClasspathFileWithoutKey() {
resolver = new PropertiesFileResolver(env, "localconfig.testuris.properties");
assertNull(resolver.findCloudPropertiesFileFromClasspath());
}
@Test
public void testLiteral() {
resolver = new PropertiesFileResolver(env, "spring-cloud-literal.properties");
assertEquals(PROPERTIES_FILE_NAME,
resolver.findCloudPropertiesFileFromClasspath().getPath());
}
@Test
public void testTemplate() {
resolver = new PropertiesFileResolver(env, "spring-cloud-template.properties");
env.setSystemProperty("user.home", "/foo");
assertEquals(PROPERTIES_FILE_NAME,
resolver.findCloudPropertiesFileFromClasspath().getPath());
}
@Test
public void testFromSystem() {
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME);
assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFile().getPath());
}
@Test
public void testFromClasspath() {
resolver = new PropertiesFileResolver(env, "spring-cloud-template.properties");
env.setSystemProperty("user.home", "/foo");
assertEquals(PROPERTIES_FILE_NAME,
resolver.findCloudPropertiesFile().getPath());
}
@Test
public void testNowhere() {
assertNull(resolver.findCloudPropertiesFile());
}
@Test
public void testPrecedence() {
env.setSystemProperty(LocalConfigConnector.PROPERTIES_FILE_PROPERTY, PROPERTIES_FILE_NAME);
resolver = new PropertiesFileResolver(env, "spring-cloud-literal.properties");
assertEquals(PROPERTIES_FILE_NAME, resolver.findCloudPropertiesFile().getPath());
}
}

View File

@@ -0,0 +1,65 @@
package org.springframework.cloud.localconfig;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* Provides an easy way to stub the {@code openFile} method on the local connector.
*
* @author Christopher Smith
*
*/
class StubbedOpenFileLocalConfigConnector extends LocalConfigConnector {
static final Charset UTF_8 = Charset.forName("UTF-8");
private InputStreamProvider fileProvider;
@Override
InputStream openFile(File file) throws IOException {
return fileProvider.openFile(file);
}
public void setFileProvider(InputStreamProvider provider) {
this.fileProvider = provider;
}
interface InputStreamProvider {
InputStream openFile(File file) throws IOException;
}
/**
* Returns the supplied input stream. Used for reading out of the classpath for testing.
*
* @param filename
* the filename we expect the connector to open
* @param contents
* the contents to return
*/
static InputStreamProvider fileContentsFromStream(final String expectedFilename, final InputStream stream) {
return new InputStreamProvider() {
@Override
public InputStream openFile(File file) throws IOException {
assertEquals(expectedFilename, file.getPath());
return stream;
}
};
}
/**
* Returns a stream view of the provided string.
*
* @param filename
* the filename we expect the connector to open
* @param contents
* the contents to return
*/
static InputStreamProvider fileContentsFromString(final String expectedFilename, final String contents) {
return fileContentsFromStream(expectedFilename, new ByteArrayInputStream(contents.getBytes(UTF_8)));
}
}

View File

@@ -0,0 +1 @@
spring.cloud.propertiesFile=/foo/bar.properties

View File

@@ -0,0 +1 @@
spring.cloud.propertiesFile=${user.home}/bar.properties