Merge branch 'master' into heartbeat-event

This commit is contained in:
Oleg Vyukov
2018-04-23 15:50:08 +03:00
39 changed files with 1051 additions and 659 deletions

View File

@@ -1,66 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<servers>
<server>
<id>repo.spring.io</id>
<username>${env.CI_DEPLOY_USERNAME}</username>
<password>${env.CI_DEPLOY_PASSWORD}</password>
</server>
</servers>
<profiles>
<profile>
<!--
N.B. this profile is only here to support users and IDEs that do not use Maven 3.3.
It isn't needed on the command line if you use the wrapper script (mvnw) or if you use
a native Maven with the right version. Eclipse users should points their Maven tooling to
this settings file, or copy the profile into their ~/.m2/settings.xml.
-->
<id>spring</id>
<activation><activeByDefault>true</activeByDefault></activation>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>http://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>http://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>http://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<servers>
<server>
<id>repo.spring.io</id>
<username>${env.CI_DEPLOY_USERNAME}</username>
<password>${env.CI_DEPLOY_PASSWORD}</password>
</server>
</servers>
<profiles>
<profile>
<!--
N.B. this profile is only here to support users and IDEs that do not use Maven 3.3.
It isn't needed on the command line if you use the wrapper script (mvnw) or if you use
a native Maven with the right version. Eclipse users should points their Maven tooling to
this settings file, or copy the profile into their ~/.m2/settings.xml.
-->
<id>spring</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>http://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>http://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>http://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>

68
pom.xml
View File

@@ -23,7 +23,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>1.3.3.BUILD-SNAPSHOT</version>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
@@ -34,7 +34,7 @@
<name>Spring Cloud Kubernetes</name>
<url>http://cloud.spring.io</url>
<inceptionYear>2016</inceptionYear>
<inceptionYear>2017</inceptionYear>
<organization>
<name>Pivotal Software, Inc.</name>
@@ -59,17 +59,20 @@
<properties>
<!-- Dependency Versions -->
<spring-cloud-commons.version>1.2.3.RELEASE</spring-cloud-commons.version>
<spring-cloud-netflix.version>1.3.2.RELEASE</spring-cloud-netflix.version>
<spring-cloud-sleuth.version>1.2.2.RELEASE</spring-cloud-sleuth.version>
<!-- <spring-cloud-netflix.version>1.3.2.RELEASE</spring-cloud-netflix.version>
<spring-cloud-sleuth.version>1.2.2.RELEASE</spring-cloud-sleuth.version> -->
<spring-cloud-commons.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
<!-- Maven Plugin Versions -->
<maven-compiler-plugin.version>3.5</maven-compiler-plugin.version>
<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
<maven-failsafe-plugin.version>2.19.1</maven-failsafe-plugin.version>
<maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version>
<fabric8.maven.plugin.version>3.2.28</fabric8.maven.plugin.version>
<gmavenplus-plugin.version>1.2</gmavenplus-plugin.version>
<maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version>
<maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version>
<fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version>
<gmavenplus-plugin.version>1.6</gmavenplus-plugin.version>
<groovy.version>2.4.12</groovy.version>
<restassured.version>3.0.2</restassured.version>
<spock-spring.version>1.1-groovy-2.4</spock-spring.version>
</properties>
<modules>
@@ -77,18 +80,25 @@
<module>spring-cloud-kubernetes-core</module>
<module>spring-cloud-kubernetes-config</module>
<module>spring-cloud-kubernetes-discovery</module>
<module>spring-cloud-starter-kubernetes</module>
<module>spring-cloud-starter-kubernetes-config</module>
<module>spring-cloud-kubernetes-examples</module>
<!-- <module>spring-cloud-starter-kubernetes-netflix</module>
<module>spring-cloud-starter-kubernetes-zipkin</module>
<module>spring-cloud-starter-kubernetes-all</module>
<module>spring-cloud-kubernetes-ribbon</module>
<module>spring-cloud-kubernetes-zipkin</module>
<module>spring-cloud-kubernetes-hystrix</module>
<module>spring-cloud-kubernetes-archaius</module>
<module>spring-cloud-starter-kubernetes</module>
<module>spring-cloud-starter-kubernetes-config</module>
<module>spring-cloud-starter-kubernetes-netflix</module>
<module>spring-cloud-starter-kubernetes-zipkin</module>
<module>spring-cloud-starter-kubernetes-all</module>
<module>spring-cloud-kubernetes-examples</module>
<module>spring-cloud-kubernetes-archaius</module> -->
</modules>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
@@ -108,21 +118,23 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-dependencies</artifactId>
<version>${spring-cloud-netflix.version}</version>
<type>pom</type>
<scope>import</scope>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-dependencies</artifactId>
<version>${spring-cloud-sleuth.version}</version>
<type>pom</type>
<scope>import</scope>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${restassured.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>${spock-spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -147,7 +159,7 @@
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
<goal>compileTests</goal>
</goals>
</execution>
</executions>

View File

@@ -82,7 +82,7 @@ public class ArchaiusConfigMapSourceConfiguration implements InitializingBean, D
}
watch = StringUtils.isEmpty(namespace)
? client.configMaps().withName(name).watch(watcher)
: client.configMaps().inNamespace(namespace).withName(namespace).watch(watcher);
: client.configMaps().inNamespace(namespace).withName(name).watch(watcher);
started.set(true);
}

View File

@@ -17,6 +17,16 @@
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -32,6 +42,11 @@
<artifactId>spring-boot-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
@@ -42,6 +57,11 @@
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -50,6 +70,24 @@
</dependency>
<!-- Testing Dependencies -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@@ -59,22 +97,62 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<type>test-jar</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>

View File

@@ -31,7 +31,6 @@ import io.fabric8.kubernetes.client.KubernetesClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.yaml.SpringProfileDocumentMatcher;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.util.StringUtils;
@@ -76,29 +75,41 @@ public class ConfigMapPropertySource extends KubernetesPropertySource {
: client.configMaps().inNamespace(namespace).withName(name).get();
if (map != null) {
for (Map.Entry<String, String> entry : map.getData().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key.equals(APPLICATION_YAML) || key.equals(APPLICATION_YML)) {
result.putAll(yamlParserGenerator(profiles).andThen(PROPERTIES_TO_MAP).apply(value));
} else if (key.equals(APPLICATION_PROPERTIES)) {
result.putAll(KEY_VALUE_TO_PROPERTIES.andThen(PROPERTIES_TO_MAP).apply(value));
} else {
result.put(key, value);
}
}
result.putAll(processAllEntries(map.getData(), profiles));
}
} catch (Exception e) {
LOG.warn("Can't read configMap with name: [" + name + "] in namespace:[" + namespace + "]. Ignoring", e);
}
}
// read for secrets mount
putPathConfig(result, config.getPaths());
Map<String, String> configsFromPaths = new HashMap<>();
putPathConfig(configsFromPaths, config.getPaths());
result.putAll(processAllEntries(configsFromPaths, profiles));
return result;
}
private static Map<String, String> processAllEntries(Map<String, String> input,
String[] profiles) {
return input.entrySet().stream()
.map(e -> extractProperties(e.getKey(), e.getValue(), profiles))
.filter(m -> !m.isEmpty())
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
}
private static Map<String, String> extractProperties(String resourceName, String content, String[] profiles) {
Map<String, String> result = new HashMap<>();
if (resourceName.equals(APPLICATION_YAML) || resourceName.equals(APPLICATION_YML)) {
result.putAll(yamlParserGenerator(profiles).andThen(PROPERTIES_TO_MAP).apply(content));
} else if (resourceName.equals(APPLICATION_PROPERTIES)) {
result.putAll(KEY_VALUE_TO_PROPERTIES.andThen(PROPERTIES_TO_MAP).apply(content));
} else {
result.put(resourceName, content);
}
return result;
}
private static Map<String, Object> asObjectMap(Map<String, String> source) {
return source.entrySet()
.stream()
@@ -108,11 +119,6 @@ public class ConfigMapPropertySource extends KubernetesPropertySource {
private static Function<String, Properties> yamlParserGenerator(final String[] profiles) {
return s -> {
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
if (profiles == null) {
yamlFactory.setDocumentMatchers(new SpringProfileDocumentMatcher());
} else {
yamlFactory.setDocumentMatchers(new SpringProfileDocumentMatcher(profiles));
}
yamlFactory.setResources(new ByteArrayResource(s.getBytes()));
return yamlFactory.getObject();
};

View File

@@ -17,6 +17,11 @@
package org.springframework.cloud.kubernetes.config.reload;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration;
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
@@ -39,7 +44,9 @@ import org.springframework.scheduling.annotation.EnableScheduling;
*/
@Configuration
@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
@AutoConfigureAfter({InfoEndpointAutoConfiguration.class, RefreshEndpointAutoConfiguration.class, RefreshAutoConfiguration.class})
@EnableConfigurationProperties(ConfigReloadProperties.class)
public class ConfigReloadAutoConfiguration {
/**

View File

@@ -21,7 +21,7 @@ import io.fabric8.kubernetes.api.model.SecretBuilder
import io.fabric8.kubernetes.api.model.ConfigMapBuilder
import io.fabric8.kubernetes.client.Config
import io.fabric8.kubernetes.client.KubernetesClient
import io.fabric8.kubernetes.server.mock.KubernetesMockServer
import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.test.context.SpringBootTest

View File

@@ -47,7 +47,8 @@ import static org.junit.Assert.assertEquals;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "spring.application.name=configmap-example",
"spring.cloud.kubernetes.reload.enabled=false"})
"spring.cloud.kubernetes.reload.enabled=false"}
)
public class ConfigMapsSpringBootTest {
@ClassRule

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.kubernetes.config;
import io.fabric8.kubernetes.client.utils.IOHelpers;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -88,8 +89,13 @@ public class ConfigMapsTest {
createConfigMapFile(apiPath, "api.url", "http://localhost/api");
createConfigMapFile(apiPath, "foo.bar", "42");
final Path filesPath = tmp.resolve("cm/files");
createConfigMapFile(filesPath, "application.yaml", readResourceFile("application.yaml"));
createConfigMapFile(filesPath, "application.properties", readResourceFile("application.properties"));
// parse ConfigMaps
cmConfProperties.setPaths(Arrays.asList(dbPath.toString(), apiPath.toString()));
cmConfProperties.setPaths(Arrays.asList(dbPath.toString(), apiPath.toString(), filesPath.toString()));
ConfigMapPropertySource cmps = new ConfigMapPropertySource(client, "testapp", cmConfProperties);
// assert as expected
@@ -98,7 +104,26 @@ public class ConfigMapsTest {
assertEquals("http://localhost/api", cmps.getProperty("api.url"));
assertFalse(cmps.containsProperty("no.such.property"));
FileSystemUtils.deleteRecursively(tmp.toFile());
assertEquals("a", cmps.getProperty("dummy.property.string1"));
assertEquals("1", cmps.getProperty("dummy.property.int1"));
assertEquals("true", cmps.getProperty("dummy.property.bool1"));
assertEquals("a", cmps.getProperty("dummy.property.string2"));
assertEquals("1", cmps.getProperty("dummy.property.int2"));
assertEquals("true", cmps.getProperty("dummy.property.bool2"));
FileSystemUtils.deleteRecursively(tmp.toFile());
}
private String readResourceFile(String file) {
String resource;
try {
resource = IOHelpers.readFully(getClass().getClassLoader().getResourceAsStream(file));
}
catch (IOException e) {
resource = "";
}
return resource;
}
private void createConfigMapFile(Path basePath, String key, String value) throws IOException {

View File

@@ -0,0 +1,3 @@
dummy.property.string1=a
dummy.property.int1=1
dummy.property.bool1=true

View File

@@ -0,0 +1,5 @@
dummy:
property:
string2: "a"
int2: 1
bool2: true

View File

@@ -32,6 +32,16 @@
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -81,6 +91,16 @@
<groupId>io.fabric8</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>

View File

@@ -41,7 +41,7 @@ public class KubernetesAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Config.class)
public Config kubernetesClientConfig(KubernetesClientProperties kubernetesClientProperties) {
Config base = new Config();
Config base = Config.autoConfigure(null);
Config properties = new ConfigBuilder(base)
//Only set values that have been explicitly specified
.withMasterUrl(or(kubernetesClientProperties.getMasterUrl(), base.getMasterUrl()))

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.3.3.BUILD-SNAPSHOT</version>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-kubernetes-dependencies</artifactId>
@@ -14,12 +14,10 @@
<name>Spring Cloud Kubernetes :: Dependencies</name>
<description>Spring Cloud Kubernetes Dependencies</description>
<properties>
<arquillian.version>1.1.13.Final</arquillian.version>
<arquillian-cube.version>1.3.2</arquillian-cube.version>
<kubernetes-client.version>2.4.1</kubernetes-client.version>
<mockwebserver.version>0.0.13</mockwebserver.version>
<restassured.version>3.0.2</restassured.version>
<spock-spring.version>1.0-groovy-2.3</spock-spring.version>
<arquillian.version>1.4.0.Final</arquillian.version>
<arquillian-cube.version>1.15.2</arquillian-cube.version>
<kubernetes-client.version>3.1.8</kubernetes-client.version>
<mockwebserver.version>0.1.0</mockwebserver.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -46,13 +44,13 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-archaius</artifactId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
<artifactId>spring-cloud-kubernetes-archaius</artifactId>
<version>${project.version}</version>
</dependency>
@@ -74,20 +72,6 @@
<version>${project.version}</version>
</dependency>
<!-- Own dependencies - Starters -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-netflix</artifactId>
@@ -104,6 +88,19 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
<version>${project.version}</version>
</dependency>-->
<!-- Own dependencies - Starters -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Testing Dependencies -->
@@ -137,6 +134,16 @@
<artifactId>mockwebserver</artifactId>
<version>${mockwebserver.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -145,20 +152,19 @@
<version>${kubernetes-client.version}</version>
<type>test-jar</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${restassured.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>${spock-spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>

View File

@@ -44,12 +44,10 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<optional>true</optional>
</dependency>
<!-- Testing Dependencies -->
@@ -70,12 +68,32 @@
<artifactId>kubernetes-client</artifactId>
<type>test-jar</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View File

@@ -16,83 +16,129 @@
package org.springframework.cloud.kubernetes.discovery;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import io.fabric8.kubernetes.api.model.EndpointAddress;
import io.fabric8.kubernetes.api.model.EndpointSubset;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.utils.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.util.Assert;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class KubernetesDiscoveryClient implements DiscoveryClient {
private static final String HOSTNAME = "HOSTNAME";
private static final Log log = LogFactory.getLog(KubernetesDiscoveryClient.class);
private static final String HOSTNAME = "HOSTNAME";
private KubernetesClient client;
private KubernetesDiscoveryProperties properties;
private KubernetesClient client;
private KubernetesDiscoveryProperties properties;
public KubernetesDiscoveryClient(KubernetesClient client, KubernetesDiscoveryProperties properties) {
this.client = client;
this.properties = properties;
}
public KubernetesDiscoveryClient(KubernetesClient client,
KubernetesDiscoveryProperties kubernetesDiscoveryProperties) {
this.client = client;
this.properties = properties;
}
public KubernetesClient getClient() {
return client;
}
public KubernetesClient getClient() {
return client;
}
public void setClient(KubernetesClient client) {
this.client = client;
}
public void setClient(KubernetesClient client) {
this.client = client;
}
@Override
public String description() {
return "Kubernetes Discovery Client";
}
@Override
public String description() {
return "Kubernetes Discovery Client";
}
@Override
public ServiceInstance getLocalServiceInstance() {
String serviceName = properties.getServiceName();
String podName = System.getenv(HOSTNAME);
ServiceInstance defaultInstance = new DefaultServiceInstance(serviceName, "localhost", 8080, false);
public ServiceInstance getLocalServiceInstance() {
String serviceName = properties.getServiceName();
String podName = System.getenv(HOSTNAME);
ServiceInstance defaultInstance = new DefaultServiceInstance(serviceName,
"localhost",
8080,
false);
Endpoints endpoints = client.endpoints().withName(serviceName).get();
if (Utils.isNullOrEmpty(podName) || endpoints == null) {
return defaultInstance;
}
try {
return endpoints.getSubsets()
.stream()
.filter(s -> s.getAddresses().get(0).getTargetRef().getName().equals(podName))
.map(s -> (ServiceInstance) new KubernetesServiceInstance(serviceName,
s.getAddresses().stream().findFirst().orElseThrow(IllegalStateException::new),
s.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
false))
.findFirst().orElse(defaultInstance);
} catch (Throwable t) {
return defaultInstance;
}
}
Endpoints endpoints = client.endpoints().withName(serviceName).get();
Optional<Service> service = Optional.ofNullable(client.services().withName(serviceName).get());
final Map<String, String> labels;
if (service.isPresent()) {
labels = service.get().getMetadata().getLabels();
} else {
labels = null;
}
if (Utils.isNullOrEmpty(podName) || endpoints == null) {
return defaultInstance;
}
try {
List<EndpointSubset> subsets = endpoints.getSubsets();
@Override
public List<ServiceInstance> getInstances(String serviceId) {
Assert.notNull(serviceId, "[Assertion failed] - the object argument must be null");
return Optional.ofNullable(client.endpoints().withName(serviceId).get()).orElse(new Endpoints())
.getSubsets()
.stream()
.flatMap(s -> s.getAddresses().stream().map(a -> (ServiceInstance) new KubernetesServiceInstance(serviceId, a ,s.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new), false)))
.collect(Collectors.toList());
if (subsets != null) {
for (EndpointSubset s : subsets) {
List<EndpointAddress> addresses = s.getAddresses();
for (EndpointAddress a : addresses) {
return new KubernetesServiceInstance(serviceName,
a,
s.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
labels,
false);
}
}
}
return defaultInstance;
}
} catch (Throwable t) {
return defaultInstance;
}
}
@Override
public List<String> getServices() {
return client.services().list()
.getItems()
.stream().map(s -> s.getMetadata().getName())
.collect(Collectors.toList());
}
@Override
public List<ServiceInstance> getInstances(String serviceId) {
Assert.notNull(serviceId,
"[Assertion failed] - the object argument must be null");
Optional<Service> service = Optional.ofNullable(client.services().withName(serviceId).get());
final Map<String, String> labels;
if (service.isPresent()) {
labels = service.get().getMetadata().getLabels();
} else {
labels = null;
}
Optional<Endpoints> endpoints = Optional.ofNullable(client.endpoints().withName(serviceId).get());
List<EndpointSubset> subsets = endpoints.get().getSubsets();
List<ServiceInstance> instances = new ArrayList<>();
if (subsets != null) {
for (EndpointSubset s : subsets) {
List<EndpointAddress> addresses = s.getAddresses();
for (EndpointAddress a : addresses) {
instances.add(new KubernetesServiceInstance(serviceId,
a,
s.getPorts().stream().findFirst().orElseThrow(IllegalStateException::new),
labels,
false));
}
}
}
return instances;
}
@Override
public List<String> getServices() {
return client.services().list()
.getItems()
.stream().map(s -> s.getMetadata().getName())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,45 @@
package org.springframework.cloud.kubernetes.discovery;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.kubernetes.registry.KubernetesRegistration;
import org.springframework.cloud.kubernetes.registry.KubernetesServiceRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class KubernetesDiscoveryClientAutoConfiguration {
@Bean
public DiscoveryClient discoveryClient(KubernetesClient client,
KubernetesDiscoveryProperties properties) {
return new KubernetesDiscoveryClient(client, properties);
}
@Bean
public KubernetesServiceRegistry getServiceRegistry() {
return new KubernetesServiceRegistry();
}
@Bean
public KubernetesRegistration getRegistration(KubernetesClient client,
KubernetesDiscoveryProperties properties) {
return new KubernetesRegistration(client, properties);
}
@Bean
@Primary
public KubernetesDiscoveryProperties getKubernetesDiscoveryProperties() {
return new KubernetesDiscoveryProperties();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled", matchIfMissing = true)
public KubernetesCatalogWatch kubernetesCatalogWatch(KubernetesDiscoveryClient client) {
return new KubernetesCatalogWatch(client);
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright (C) 2016 to the original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.discovery;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(KubernetesDiscoveryProperties.class)
@ConditionalOnProperty(value = "spring.cloud.kubernetes.discovery.enabled", matchIfMissing = true)
public class KubernetesDiscoveryClientConfiguration {
@Bean
@ConditionalOnMissingBean
public KubernetesDiscoveryClient kubernetesDiscoveryClient(KubernetesClient client, KubernetesDiscoveryProperties properties) {
return new KubernetesDiscoveryClient(client, properties);
}
@Bean
public KubernetesDiscoveryLifecycle kubernetesDiscoveryLifecycle(KubernetesClient client, KubernetesDiscoveryProperties properties) {
return new KubernetesDiscoveryLifecycle(client, properties);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled", matchIfMissing = true)
public KubernetesCatalogWatch kubernetesCatalogWatch(KubernetesDiscoveryClient client) {
return new KubernetesCatalogWatch(client);
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright (C) 2016 to the original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.discovery;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import java.util.concurrent.atomic.AtomicBoolean;
public class KubernetesDiscoveryLifecycle extends AbstractDiscoveryLifecycle {
private KubernetesClient client;
private KubernetesDiscoveryProperties properties;
private AtomicBoolean running = new AtomicBoolean(false);
public KubernetesDiscoveryLifecycle(KubernetesClient client, KubernetesDiscoveryProperties properties) {
this.client = client;
this.properties = properties;
}
@Override
public void start() {
if (!isEnabled()) {
return;
}
if (running.compareAndSet(false, true)) {
register();
getContext().publishEvent(new InstanceRegisteredEvent<>(this,
getConfiguration()));
}
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
protected int getConfiguredPort() {
return client.getMasterUrl().getPort();
}
@Override
protected void setConfiguredPort(int port) {
}
@Override
protected Object getConfiguration() {
return properties;
}
@Override
protected void register() {
}
@Override
protected void deregister() {
}
@Override
protected boolean isEnabled() {
return properties.isEnabled();
}
}

View File

@@ -18,34 +18,33 @@ package org.springframework.cloud.kubernetes.discovery;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
public class KubernetesDiscoveryProperties {
public class KubernetesDiscoveryProperties extends AutoServiceRegistrationProperties {
private boolean enabled = true;
private boolean enabled = true;
@Value("${spring.application.name:unknown}")
private String serviceName = "unknown";
@Value("${spring.application.name:unknown}")
private String serviceName = "unknown";
private int catalogServicesWatchDelay = 30000;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getServiceName() {
return serviceName;
}
public int getCatalogServicesWatchDelay() {
return catalogServicesWatchDelay;
public boolean isEnabled() {
return enabled;
}
public void setCatalogServicesWatchDelay(int catalogServicesWatchDelay) {
this.catalogServicesWatchDelay = catalogServicesWatchDelay;
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getServiceName() {
return serviceName;
}
@Override
public String toString() {
return "KubernetesDiscoveryProperties{" +
"enabled=" + enabled +
", serviceName='" + serviceName + '\'' +
'}';
}
}

View File

@@ -16,75 +16,77 @@
package org.springframework.cloud.kubernetes.discovery;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import io.fabric8.kubernetes.api.model.EndpointAddress;
import io.fabric8.kubernetes.api.model.EndpointPort;
import org.springframework.cloud.client.ServiceInstance;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Map;
import static io.fabric8.kubernetes.client.utils.Utils.isNotNullOrEmpty;
import static io.fabric8.kubernetes.client.utils.Utils.isNullOrEmpty;
public class KubernetesServiceInstance implements ServiceInstance {
private static final String HTTP_PREFIX = "http://";
private static final String HTTPS_PREFIX = "https://";
private static final String COLN = ":";
private static final String HTTP_PREFIX = "http://";
private static final String HTTPS_PREFIX = "https://";
private static final String COLN = ":";
private final String serviceId;
private final EndpointAddress endpointAddress;
private final EndpointPort endpointPort;
private final Boolean secure;
private final String serviceId;
private final EndpointAddress endpointAddress;
private final EndpointPort endpointPort;
private final Boolean secure;
private final Map<String, String> metadata;
public KubernetesServiceInstance(String serviceId, EndpointAddress endpointAddress, EndpointPort endpointPort, Boolean secure) {
this.serviceId = serviceId;
this.endpointAddress = endpointAddress;
this.endpointPort = endpointPort;
this.secure = secure;
}
public KubernetesServiceInstance(String serviceId,
EndpointAddress endpointAddress,
EndpointPort endpointPort,
Map<String, String> metadata,
Boolean secure) {
this.serviceId = serviceId;
this.endpointAddress = endpointAddress;
this.endpointPort = endpointPort;
this.metadata = metadata;
this.secure = secure;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public String getHost() {
return endpointAddress.getIp();
}
@Override
public String getHost() {
return endpointAddress.getIp();
}
@Override
public int getPort() {
return endpointPort.getPort();
}
@Override
public int getPort() {
return endpointPort.getPort();
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public URI getUri() {
StringBuilder sb = new StringBuilder();
@Override
public URI getUri() {
StringBuilder sb = new StringBuilder();
if (isSecure()) {
sb.append(HTTPS_PREFIX);
} else {
sb.append(HTTP_PREFIX);
}
if (isSecure()) {
sb.append(HTTPS_PREFIX);
} else {
sb.append(HTTP_PREFIX);
}
sb.append(getHost()).append(COLN).append(getPort());
try {
return new URI(sb.toString());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
sb.append(getHost()).append(COLN).append(getPort());
try {
return new URI(sb.toString());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public Map<String, String> getMetadata() {
return Collections.EMPTY_MAP;
}
public Map<String, String> getMetadata() {
return metadata;
}
}

View File

@@ -0,0 +1,103 @@
package org.springframework.cloud.kubernetes.registry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
public class KubernetesAutoServiceRegistration implements AutoServiceRegistration,
SmartLifecycle,
Ordered {
private static final Log log = LogFactory.getLog(KubernetesAutoServiceRegistration.class);
private AtomicBoolean running = new AtomicBoolean(false);
private int order = 0;
private AtomicInteger port = new AtomicInteger(0);
private ApplicationContext context;
private KubernetesServiceRegistry serviceRegistry;
private KubernetesRegistration registration;
public KubernetesAutoServiceRegistration(ApplicationContext context,
KubernetesServiceRegistry serviceRegistry,
KubernetesRegistration registration) {
this.context = context;
this.serviceRegistry = serviceRegistry;
this.registration = registration;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
@Override
public void start() {
this.serviceRegistry.register(this.registration);
this.context.publishEvent(
new InstanceRegisteredEvent<>(this,
this.registration.getProperties()));
this.running.set(true);
}
@Override
public void stop() {
this.serviceRegistry.deregister(this.registration);
this.running.set(false);
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
public int getPhase() {
return 0;
}
@Override
public int getOrder() {
return 0;
}
@EventListener(ServletWebServerInitializedEvent.class)
public void onApplicationEvent(ServletWebServerInitializedEvent event) {
// TODO: take SSL into account
int localPort = event.getWebServer().getPort();
if (this.port.get() == 0) {
log.info("Updating port to " + localPort);
this.port.compareAndSet(0,
localPort);
start();
}
}
@EventListener(ContextClosedEvent.class)
public void onApplicationEvent(ContextClosedEvent event) {
if (event.getApplicationContext() == context) {
stop();
}
}
}

View File

@@ -0,0 +1,79 @@
package org.springframework.cloud.kubernetes.registry;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
public class KubernetesRegistration implements Registration, Closeable {
private final KubernetesClient client;
private KubernetesDiscoveryProperties properties;
private AtomicBoolean running = new AtomicBoolean(false);
public KubernetesRegistration(KubernetesClient client,
KubernetesDiscoveryProperties properties) {
this.client = client;
this.properties = properties;
}
@Override
public void close() throws IOException {
this.client.close();
}
@Override
public String getServiceId() {
return properties.getServiceName();
}
@Override
public String getHost() {
return client.getMasterUrl().getHost();
}
@Override
public int getPort() {
return 0;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public URI getUri() {
try {
return client.getMasterUrl().toURI();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
public KubernetesDiscoveryProperties getProperties() {
return properties;
}
@Override
public Map<String, String> getMetadata() {
return null;
}
@Override
public String toString() {
return "KubernetesRegistration{" +
"client=" + client +
", properties=" + properties +
", running=" + running +
'}';
}
}

View File

@@ -0,0 +1,41 @@
package org.springframework.cloud.kubernetes.registry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
public class KubernetesServiceRegistry implements ServiceRegistry<KubernetesRegistration> {
private static final Log log = LogFactory.getLog(KubernetesServiceRegistry.class);
public KubernetesServiceRegistry() {
}
@Override
public void register(KubernetesRegistration registration) {
log.info("Registering : " + registration);
}
@Override
public void deregister(KubernetesRegistration registration) {
log.info("DeRegistering : " + registration);
}
@Override
public void close() {
}
@Override
public void setStatus(KubernetesRegistration registration,
String status) {
log.info("Set Status for : " + registration + " Status: " + status);
}
@Override
public <T> T getStatus(KubernetesRegistration registration) {
log.info("Get Status for : " + registration );
return null;
}
}

View File

@@ -1,3 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientAutoConfiguration
# Discovery Client Configuration
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClientConfiguration
org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClient

View File

@@ -24,7 +24,7 @@ import static org.mockito.Mockito.mock;
/**
* @author Oleg Vyukov
*/
public class KubernetesCatalogServicesWatchtConfigurationTest {
public class KubernetesCatalogServicesWatchConfigurationTest {
private ConfigurableApplicationContext context;
@@ -51,7 +51,7 @@ public class KubernetesCatalogServicesWatchtConfigurationTest {
this.context = new SpringApplicationBuilder(
PropertyPlaceholderAutoConfiguration.class,
KubernetesClientTestConfiguration.class,
KubernetesDiscoveryClientConfiguration.class).web(false)
KubernetesDiscoveryClientAutoConfiguration.class).web(false)
.properties(env).run();
}

View File

@@ -1,3 +1,21 @@
# Setting up the Environment
To play with these examples, you can install locally Kubernetes & Docker using `[Minikube](https://kubernetes.io/docs/getting-started-guides/minikube/)` within a Virtual Machine
managed by a hypervisor (Xhyve, Virtualbox or KVM) if your machine is not a native Unix operating system.
When the minikube is installed on your machine, you can start kubernetes using this command:
```
minikube start
```
You also probably want to configure your docker client to point the minikube docker deamon with:
```
eval $(minikube docker-env)
```
This will make sure that the docker images that you build are available to the minikube environment.
# Hello World Example
This Spring Boot application exposes an endpoint that we can call to receive a `Hello World` message as response. The application is configured using the
@@ -6,25 +24,18 @@ This Spring Boot application exposes an endpoint that we can call to receive a `
The uberjar of the Spring Boot application is packaged within a Docker image using the [Fabric8 Maven plugin](maven.fabric8.io) and next deployed top of the Kubernetes management platform as a pod
using the replication controller created by the plugin.
To play with the example, it is required to have access to a Kubernetes Management Platform which is available on GCE. If you don't have an account on GCE,
you can install locally Kubernetes & Docker using `[Minikube](https://kubernetes.io/docs/getting-started-guides/minikube/)` within a Virtual Machine
managed by a hypervisor (Xhyve, Virtualbox or KVM) if your machine is not a native Unix operating system.
The script to install `minikube` is
```
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.17.1/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
```
When the client is installed on your machine, you can start kubernetes using this command:
```
minikube start
```
Once you have the environment set up (minikube or kubectl configured against a kubernetes cluster)
Next, you can play with this Spring Boot application in the cloud using the following maven command to deploy it:
You can play with this Spring Boot application in the cloud using the following maven command to deploy it:
```
mvn clean package fabric8:deploy -Pkubernetes
```
**Note**: Unfortuntaly, when you deploy using the fabric8 plugin, the readyness and liveness probes fail to point to the right actuator URL due a lack of support for spring boot.
This push you to edit the generated deployment inside kubernetes and change these probes which points to "path": "/health" to "path": "/actuator/health".
This will make your deployment go green. This issue is already reported into the fabric8 community: https://github.com/fabric8io/fabric8-maven-plugin/issues/1178
When the application has been deployed, you can access its service or endpoint url using this command:
```
minikube service kubernetes-hello-world --url
@@ -35,19 +46,15 @@ And next you can curl the endpoint using the url returned by the previous comman
```
curl http://IP_OR_HOSTNAME/
```
then
```
curl http://IP_OR_HOSTNAME/services
```
Should return you the list of available services discovered by the DiscoveryClient
## Integration test
To deploy the resources required on Kubernetes/OpenShift and to test if the service deployed can answer to requests, we will use the [Arquillian Kubernetes Cube](https://github.com/arquillian/arquillian-cube/blob/master/docs/kubernetes.adoc) framework.
This testing framework uses the Kubernetes Java API to communicate with the platform, deploy the resources (pod, service, deployment, ...) generated by the Fabric8 maven plugin.
When the service becomes available, the method defines within thr Junit test will call the endpoint to verify that it receives as response the string
`Hello World`
Remark : If you run the integration test against OpenShift, create first the namespace/project `it` that arquillian will use to deploy the resources using the oc client
`oc new-project it`.
When you are logged to the OpenShift platform, execute the following maven command to run the Integration Test against OpenShift
```
mvn clean install -Pintegration
```

View File

@@ -35,6 +35,17 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
@@ -57,23 +68,23 @@
<!-- Testing -->
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-standalone</artifactId>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-requirement</artifactId>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-kubernetes</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-openshift</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>

View File

@@ -18,16 +18,17 @@ package org.springframework.cloud.kubernetes.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
*
*/
@SpringBootApplication
@EnableDiscoveryClient
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
public static void main(String[] args) {
SpringApplication.run(App.class,
args);
}
}

View File

@@ -1,14 +1,30 @@
package org.springframework.cloud.kubernetes.examples;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
private static final Log log = LogFactory.getLog(HelloController.class);
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/")
public String hello() {
return "Hello World";
}
@RequestMapping("/services")
public List<String> services() {
return this.discoveryClient.getServices();
}
}

View File

@@ -0,0 +1,27 @@
package org.springframework.cloud.kubernetes.examples;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.*;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class ApplicationTestIT {
@Autowired
private ApplicationContext context;
/*
* This test proves that the application can be loaded successful and that all @configurations and dependencies are there
*/
@Test
public void contextLoads() throws Exception {
assertThat(context).isNotNull();
}
}

View File

@@ -1,39 +0,0 @@
package org.springframework.cloud.kubernetes.examples;
import org.arquillian.cube.kubernetes.annotations.PortForward;
import org.arquillian.cube.kubernetes.impl.requirement.RequiresKubernetes;
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.net.URL;
import javax.inject.Named;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Service;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@RunWith(ArquillianConditionalRunner.class)
@RequiresKubernetes
public class HelloWorldIT {
@ArquillianResource
@Named("kubernetes-hello-world") //The service name is "${project.artifactId}".substring(0,23)
@PortForward
URL url;
@Test
public void service_should_be_accessible() throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().get().url(url).build();
Response response = client.newCall(request).execute();
Assert.assertTrue(response.isSuccessful());
}
}

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/schema/arquillian"
xsi:schemaLocation="http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<extension qualifier="openshift">
<property name="namespace.use.existing">it</property>
<!-- <property name="wait.timeout">600000</property>-->
<property name="env.config.resource.name">openshift.yml</property>
<property name="namespace.cleanup.enabled">true</property>
</extension>
</arquillian>

View File

@@ -0,0 +1,70 @@
# Setting up the Environment
To play with these examples, you can install locally Kubernetes & Docker using `[Minikube](https://kubernetes.io/docs/getting-started-guides/minikube/)` within a Virtual Machine
managed by a hypervisor (Xhyve, Virtualbox or KVM) if your machine is not a native Unix operating system.
When the minikube is installed on your machine, you can start kubernetes using this command:
```
minikube start
```
You also probably want to configure your docker client to point the minikube docker deamon with:
```
eval $(minikube docker-env)
```
This will make sure that the docker images that you build are available to the minikube environment.
## Kubernetes Reload Example
This example demonstrate how to use the reload feature to change the configuration of a spring-boot application at runtime.
The application consists of a timed bean that periodically prints a message to the console.
The message can be changed using a config map.
### Running the example
Once you have your environment set up, you can deploy the application using the fabric8 maven plugin:
```
mvn clean install fabric8:build fabric8:deploy -Pintegration
```
**Note**: Unfortuntaly, when you deploy using the fabric8 plugin, the readyness and liveness probes fail to point to the right actuator URL due a lack of support for spring boot.
This push you to edit the generated deployment inside kubernetes and change these probes which points to "path": "/health" to "path": "/actuator/health".
This will make your deployment go green. This issue is already reported into the fabric8 community: https://github.com/fabric8io/fabric8-maven-plugin/issues/1178
### Changing the configuration
Create a yaml file with the following contents:
```yml
apiVersion: v1
kind: ConfigMap
metadata:
name: reload-example
data:
application.properties: |-
bean.message=Hello World!
another.property=value
```
A sample config map is provided with this example in the *config-map.yml* file.
To deploy the config map, just run the following command on Kubernetes:
```
kubectl create -f config-map.yml
```
As soon as the config map is deployed, the output of the application changes accordingly.
The config map can be now edited with the following command:
```
kubectl edit configmap reload-example
```
Changes are applied immediately when using the *event* reload mode.
The name of the config map (*"reload-example"*) matches the name of the application as declared in the *application.properties* file.

View File

@@ -1,109 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>0.2.1.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>0.2.1.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-kubernetes-example-reload</artifactId>
<artifactId>spring-cloud-kubernetes-example-reload</artifactId>
<name>Spring Cloud Kubernetes :: Examples :: Reload ConfigMap</name>
<description>Example demonstrating how to use the configuration reload feature.</description>
<name>Spring Cloud Kubernetes :: Examples :: Reload ConfigMap</name>
<description>Example demonstrating how to use the configuration reload feature.</description>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<type>pom</type>
<scope>import</scope>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<type>pom</type>
<scope>import</scope>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<hibernate-validator.version>5.2.4.Final</hibernate-validator.version>
</properties>
<properties>
<hibernate-validator.version>5.2.4.Final</hibernate-validator.version>
</properties>
<dependencies>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
</dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<!--skip deploy -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<!--skip deploy -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<version>${fabric8.maven.plugin.version}</version>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal>
</goals>
</execution>
</executions>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<version>${fabric8.maven.plugin.version}</version>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal>
</goals>
</execution>
</executions>
<configuration>
<enricher>
<config>
@@ -116,9 +109,9 @@
</config>
</enricher>
</configuration>
</plugin>
</plugins>
</build>
</plugin>
</plugins>
</build>
<profiles>
<profile>
@@ -143,7 +136,7 @@
<config>
<fmp-controller>
<name>spring-cloud-reload</name>
</fmp-controller>
</fmp-controller>
<fmp-service>
<name>spring-cloud-reload</name>
<type>NodePort</type>

View File

@@ -1,54 +0,0 @@
## Kubernetes Reload Example
This example demonstrate how to use the reload feature to change the configuration of a spring-boot application at runtime.
The application consists of a timed bean that periodically prints a message to the console.
The message can be changed using a config map.
### Running the example
When using Openshift, you must assign the `view` role to the *default* service account in the current project:
```
oc policy add-role-to-user view --serviceaccount=default
```
You can deploy the application using the fabric8 maven plugin:
```
mvn clean install fabric8:build fabric8:deploy -Pintegration
```
### Changing the configuration
Create a yaml file with the following contents:
```yml
apiVersion: v1
kind: ConfigMap
metadata:
name: reload-example
data:
application.properties: |-
bean.message=Hello World!
another.property=value
```
A sample config map is provided with this example in the *config-map.yml* file.
To deploy the config map, just run the following command on Openshift (just replace `oc` with `kubectl` if you are using plain Kubernetes):
```
oc create -f config-map.yml
```
As soon as the config map is deployed, the output of the application changes accordingly.
The config map can be now edited with the following command:
```
oc edit configmap reload-example
```
Changes are applied immediately when using the *event* reload mode.
The name of the config map (*"reload-example"*) matches the name of the application as declared in the *application.properties* file.

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.kubernetes.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
/**

View File

@@ -1,5 +1,7 @@
spring.application.name=reload-example
management.endpoint.health.enabled=true
management.endpoint.info.enabled=true
management.endpoint.restart.enabled=true
spring.cloud.kubernetes.reload.enabled=true
#spring.cloud.kubernetes.reload.strategy=restart_context

View File

@@ -18,8 +18,8 @@
<modules>
<module>kubernetes-reload-example</module>
<module>kubernetes-hello-world-example</module>
<module>kubernetes-circuitbreaker-ribbon-example</module>
<module>kubernetes-zipkin-example</module>
<!-- <module>kubernetes-circuitbreaker-ribbon-example</module>
<module>kubernetes-zipkin-example</module> -->
</modules>
<build>