support Reactor-based CF Java Client (#22)
* reactive Cloud Foundry Java Client - new support for auto-configuring the reactor-based CF java client v3.3. - reworkd the `DiscoveryClient` to depend on the standalone support. - made the tests run conditionally based on presence of certain env vars. fixes gh-21
This commit is contained in:
13
README.adoc
13
README.adoc
@@ -12,6 +12,9 @@ middlware that you "bind" to an app, essentially providing it with an
|
||||
environment variable containing credentials (e.g. the location and
|
||||
username to use for the service).
|
||||
|
||||
The `spring-cloud-cloudfoundry-commons` module configures the
|
||||
Reactor-based Cloud Foundry Java client, v 3.0, and can be used standalone.
|
||||
|
||||
The `spring-cloud-cloudfoundry-web` project provides basic support for
|
||||
some enhanced features of webapps in Cloud Foundry: binding
|
||||
automatically to single-sign-on services and optionally enabling
|
||||
@@ -20,16 +23,14 @@ sticky routing for discovery.
|
||||
The `spring-cloud-cloudfoundry-discovery` project provides an
|
||||
implementation of Spring Cloud Commons `DiscoveryClient` so you can
|
||||
`@EnableDiscoveryClient` and provide your credentials as
|
||||
`spring.cloud.cloudfoundry.discovery.[email,password]` and then you
|
||||
can use the `DiscoveryClient` directly or via a `LoadBalancerClient`
|
||||
(also `*.url` if you are not connecting to
|
||||
https://run.pivotal.io[Pivotal Web Services]).
|
||||
`spring.cloud.cloudfoundry.discovery.[username,password]` (also `*.url` if you are not connecting to https://run.pivotal.io[Pivotal Web Services]) and then you
|
||||
can use the `DiscoveryClient` directly or via a `LoadBalancerClient`.
|
||||
|
||||
> NOTE: if you are looking for a way to bind to services then this is the wrong library. Check out the [Spring Cloud Connectors](https://github.com/spring-cloud/spring-cloud-connectors) instead.
|
||||
|
||||
== Building
|
||||
|
||||
:jdkversion: 1.7
|
||||
:jdkversion: 1.8
|
||||
|
||||
=== Basic Compile and Test
|
||||
|
||||
@@ -70,7 +71,7 @@ https://github.com/spring-cloud-samples/scripts[scripts demo
|
||||
repository] for specific instructions about the common cases of mongo,
|
||||
rabbit and redis.
|
||||
|
||||
NOTE: If all else fails, build with the command from `.travis.yml` (usually
|
||||
NOTE: If all else fails, build with the command from `.circle.yml` (usually
|
||||
`./mvnw install`).
|
||||
|
||||
=== Documentation
|
||||
|
||||
@@ -6,6 +6,9 @@ middlware that you "bind" to an app, essentially providing it with an
|
||||
environment variable containing credentials (e.g. the location and
|
||||
username to use for the service).
|
||||
|
||||
The `spring-cloud-cloudfoundry-commons` module configures the
|
||||
Reactor-based Cloud Foundry Java client, v 3.0, and can be used standalone.
|
||||
|
||||
The `spring-cloud-cloudfoundry-web` project provides basic support for
|
||||
some enhanced features of webapps in Cloud Foundry: binding
|
||||
automatically to single-sign-on services and optionally enabling
|
||||
@@ -14,7 +17,5 @@ sticky routing for discovery.
|
||||
The `spring-cloud-cloudfoundry-discovery` project provides an
|
||||
implementation of Spring Cloud Commons `DiscoveryClient` so you can
|
||||
`@EnableDiscoveryClient` and provide your credentials as
|
||||
`spring.cloud.cloudfoundry.discovery.[email,password]` and then you
|
||||
can use the `DiscoveryClient` directly or via a `LoadBalancerClient`
|
||||
(also `*.url` if you are not connecting to
|
||||
https://run.pivotal.io[Pivotal Web Services]).
|
||||
`spring.cloud.cloudfoundry.discovery.[username,password]` (also `*.url` if you are not connecting to https://run.pivotal.io[Pivotal Web Services]) and then you
|
||||
can use the `DiscoveryClient` directly or via a `LoadBalancerClient`.
|
||||
|
||||
158
pom.xml
158
pom.xml
@@ -12,14 +12,15 @@
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<bintray.package>cloudfoundry</bintray.package>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<spring-cloud-netflix.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-netflix.version>
|
||||
<spring-cloud-commons.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
|
||||
</properties>
|
||||
<properties>
|
||||
<cf-java-client.version>3.3.0.RELEASE</cf-java-client.version>
|
||||
<bintray.package>cloudfoundry</bintray.package>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<spring-cloud-netflix.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-netflix.version>
|
||||
<spring-cloud-commons.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-consul</url>
|
||||
@@ -34,6 +35,7 @@
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-cloudfoundry-dependencies</module>
|
||||
<module>spring-cloud-cloudfoundry-commons</module>
|
||||
<module>spring-cloud-cloudfoundry-discovery</module>
|
||||
<module>spring-cloud-cloudfoundry-web</module>
|
||||
<module>spring-cloud-cloudfoundry-sample</module>
|
||||
@@ -70,80 +72,80 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons-dependencies</artifactId>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons-dependencies</artifactId>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.1.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.1.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
||||
51
spring-cloud-cloudfoundry-commons/pom.xml
Normal file
51
spring-cloud-cloudfoundry-commons/pom.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-cloudfoundry-commons</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<description>Spring Cloud CloudFoundry Commons</description>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cloudfoundry</groupId>
|
||||
<artifactId>cloudfoundry-client-reactor</artifactId>
|
||||
<version>${cf-java-client.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cloudfoundry</groupId>
|
||||
<artifactId>cloudfoundry-operations</artifactId>
|
||||
<version>${cf-java-client.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cloudfoundry</groupId>
|
||||
<artifactId>cloudfoundry-util</artifactId>
|
||||
<version>${cf-java-client.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.springframework.cloud.cloudfoundry;
|
||||
|
||||
import org.cloudfoundry.client.CloudFoundryClient;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
|
||||
import org.cloudfoundry.reactor.ConnectionContext;
|
||||
import org.cloudfoundry.reactor.DefaultConnectionContext;
|
||||
import org.cloudfoundry.reactor.TokenProvider;
|
||||
import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient;
|
||||
import org.cloudfoundry.reactor.doppler.ReactorDopplerClient;
|
||||
import org.cloudfoundry.reactor.routing.ReactorRoutingClient;
|
||||
import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider;
|
||||
import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
|
||||
import org.cloudfoundry.routing.RoutingClient;
|
||||
import org.cloudfoundry.uaa.UaaClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
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;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/**
|
||||
* Provides auto-configuration for the Reactor-based Cloud Foundry client v3.x.
|
||||
*
|
||||
* @author <a href="mailto:josh@joshlong.com">Josh Long</a>
|
||||
* @author Ben Hale
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "spring.cloud.cloudfoundry", name = {"username", "password", "org", "space"})
|
||||
@ConditionalOnClass(name = {"reactor.core.publisher.Flux", "org.cloudfoundry.operations.DefaultCloudFoundryOperations",
|
||||
"org.cloudfoundry.reactor.client.ReactorCloudFoundryClient", "org.reactivestreams.Publisher"})
|
||||
@EnableConfigurationProperties(CloudFoundryProperties.class)
|
||||
public class CloudFoundryClientAutoConfiguration {
|
||||
|
||||
private final CloudFoundryProperties cloudFoundryProperties;
|
||||
|
||||
public CloudFoundryClientAutoConfiguration(CloudFoundryProperties cfp) {
|
||||
this.cloudFoundryProperties = cfp;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public CloudFoundryService cloudFoundryService(CloudFoundryOperations cloudFoundryOperations) {
|
||||
return new CloudFoundryService(cloudFoundryOperations);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorCloudFoundryClient cloudFoundryClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorCloudFoundryClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public DefaultCloudFoundryOperations cloudFoundryOperations(CloudFoundryClient cloudFoundryClient,
|
||||
DopplerClient dopplerClient,
|
||||
RoutingClient routingClient,
|
||||
UaaClient uaaClient) {
|
||||
String organization = this.cloudFoundryProperties.getOrg();
|
||||
String space = this.cloudFoundryProperties.getSpace();
|
||||
return DefaultCloudFoundryOperations
|
||||
.builder()
|
||||
.cloudFoundryClient(cloudFoundryClient)
|
||||
.dopplerClient(dopplerClient)
|
||||
.routingClient(routingClient)
|
||||
.uaaClient(uaaClient)
|
||||
.organization(organization)
|
||||
.space(space)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public DefaultConnectionContext connectionContext() {
|
||||
|
||||
String apiHost = this.cloudFoundryProperties.getUrl();
|
||||
Boolean skipSslValidation = this.cloudFoundryProperties.isSkipSslValidation();
|
||||
|
||||
return DefaultConnectionContext.builder()
|
||||
.apiHost(apiHost)
|
||||
.skipSslValidation(skipSslValidation)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public DopplerClient dopplerClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorDopplerClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public RoutingClient routingClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorRoutingClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public PasswordGrantTokenProvider tokenProvider() {
|
||||
String username = this.cloudFoundryProperties.getUsername();
|
||||
String password = this.cloudFoundryProperties.getPassword();
|
||||
return PasswordGrantTokenProvider.builder()
|
||||
.password(password)
|
||||
.username(username)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorUaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorUaaClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.springframework.cloud.cloudfoundry;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:josh@joshlong.com">Josh Long</a>
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.cloudfoundry")
|
||||
public class CloudFoundryProperties implements InitializingBean {
|
||||
|
||||
/**
|
||||
* URL of Cloud Foundry API (Cloud Controller).
|
||||
*/
|
||||
private String url = "api.run.pivotal.io";
|
||||
|
||||
/**
|
||||
* Username to authenticate (usually an email address).
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Password for user to authenticate and obtain token.
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Organization name to authenticate with (default to user's default).
|
||||
*/
|
||||
private String org;
|
||||
|
||||
/**
|
||||
* Space name to authenticate with (default to user's default).
|
||||
*/
|
||||
@Value("${vcap.application.space_name:}")
|
||||
private String space;
|
||||
|
||||
private boolean skipSslValidation;
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
private String safeUrl(String t) {
|
||||
String input = t.trim().toLowerCase();
|
||||
Pattern p = Pattern.compile("(http(s)?://)(.*)");
|
||||
Matcher matcher = p.matcher(input);
|
||||
if (matcher.matches()) {
|
||||
String group = matcher.group(1);
|
||||
if (StringUtils.hasText(group)) {
|
||||
return t.substring(group.length());
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
public void setUrl(String cloudControllerUrl) {
|
||||
this.url = cloudControllerUrl;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String email) {
|
||||
this.username = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getOrg() {
|
||||
return this.org;
|
||||
}
|
||||
|
||||
public void setOrg(String org) {
|
||||
this.org = org;
|
||||
}
|
||||
|
||||
public String getSpace() {
|
||||
return this.space;
|
||||
}
|
||||
|
||||
public void setSpace(String space) {
|
||||
this.space = space;
|
||||
}
|
||||
|
||||
public boolean isSkipSslValidation() {
|
||||
return skipSslValidation;
|
||||
}
|
||||
|
||||
public boolean getSkipSslValidation() {
|
||||
return this.skipSslValidation;
|
||||
}
|
||||
|
||||
public void setSkipSslValidation(boolean skipSslValidation) {
|
||||
this.skipSslValidation = skipSslValidation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.url = safeUrl(this.url);
|
||||
this.password = this.password.trim();
|
||||
this.username = this.username.trim();
|
||||
this.org = this.org.trim();
|
||||
this.space = this.space.trim();
|
||||
|
||||
Map<String, String> vals = new HashMap<>();
|
||||
vals.put("org", getOrg());
|
||||
vals.put("url", getUrl());
|
||||
vals.put("username", getUsername());
|
||||
vals.put("password", getPassword());
|
||||
vals.put("space", getSpace());
|
||||
vals.forEach((key, value) -> Assert.hasText(value, String.format("'%s' must be provided", key)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.springframework.cloud.cloudfoundry;
|
||||
|
||||
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.InstanceDetail;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
/**
|
||||
* Supports the discovery of a combination of an application instance's URI, port,
|
||||
* application ID, and application index.
|
||||
*
|
||||
* @author <a href="mailto:josh@joshlong.com">Josh Long</a>
|
||||
*/
|
||||
public class CloudFoundryService {
|
||||
|
||||
private final CloudFoundryOperations cloudFoundryOperations;
|
||||
|
||||
public CloudFoundryService(CloudFoundryOperations cloudFoundryOperations) {
|
||||
this.cloudFoundryOperations = cloudFoundryOperations;
|
||||
}
|
||||
|
||||
public Flux<Tuple2<ApplicationDetail, InstanceDetail>> getApplicationInstances(String serviceId) {
|
||||
GetApplicationRequest applicationRequest = GetApplicationRequest.builder().name(serviceId).build();
|
||||
return this.cloudFoundryOperations
|
||||
.applications()
|
||||
.get(applicationRequest)
|
||||
.flatMapMany(applicationDetail -> {
|
||||
Flux<InstanceDetail> ids = Flux.fromStream(applicationDetail.getInstanceDetails().stream())
|
||||
.filter(id -> id.getState().equalsIgnoreCase("RUNNING"));
|
||||
Flux<ApplicationDetail> generate = Flux.generate(sink -> sink.next(applicationDetail));
|
||||
return generate.zipWith(ids);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.cloudfoundry.CloudFoundryClientAutoConfiguration
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.springframework.cloud.cloudfoundry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.InstanceDetail;
|
||||
import org.cloudfoundry.operations.organizations.OrganizationSummary;
|
||||
import org.cloudfoundry.reactor.DefaultConnectionContext;
|
||||
import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient;
|
||||
import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider;
|
||||
import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
|
||||
import org.cloudfoundry.routing.RoutingClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CloudFoundryClientAutoConfigurationTest {
|
||||
|
||||
@SpringBootApplication
|
||||
public static class MyConfig {
|
||||
}
|
||||
|
||||
private static String envVarFromProperty(String propertyName) {
|
||||
return propertyName
|
||||
.replaceAll("\\.", "_")
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
private static String CONFIG_KEYS[] = {"spring.cloud.cloudfoundry.username",
|
||||
"spring.cloud.cloudfoundry.password", "spring.cloud.cloudfoundry.space", "spring.cloud.cloudfoundry.org"};
|
||||
|
||||
private static Map<String, Object> defaultConfig() {
|
||||
Map<String, Object> kvs = new HashMap<>();
|
||||
for (String k : CONFIG_KEYS)
|
||||
kvs.put(k, System.getenv(envVarFromProperty(k)));
|
||||
return kvs;
|
||||
}
|
||||
|
||||
private static boolean configExists() {
|
||||
for (String k : CONFIG_KEYS)
|
||||
if (System.getenv(envVarFromProperty(k)) == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void producesAllBeans() {
|
||||
|
||||
Assume.assumeTrue(configExists());
|
||||
|
||||
ApplicationContext ctx = new SpringApplicationBuilder()
|
||||
.sources(MyConfig.class)
|
||||
.properties(defaultConfig())
|
||||
.run();
|
||||
|
||||
Class[] tags = {ReactorCloudFoundryClient.class, DefaultCloudFoundryOperations.class,
|
||||
DefaultConnectionContext.class, DopplerClient.class, RoutingClient.class, PasswordGrantTokenProvider.class,
|
||||
ReactorUaaClient.class};
|
||||
for (Class<?> c : tags) {
|
||||
Assertions.assertThat(ctx.getBean(c)).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectivity() throws Exception {
|
||||
Assume.assumeTrue(configExists());
|
||||
|
||||
ApplicationContext ctx = new SpringApplicationBuilder()
|
||||
.sources(MyConfig.class)
|
||||
.properties(defaultConfig())
|
||||
.run();
|
||||
|
||||
CloudFoundryOperations operations = ctx.getBean(CloudFoundryOperations.class);
|
||||
Assert.assertNotNull(operations);
|
||||
OrganizationSummary summary = operations
|
||||
.organizations()
|
||||
.list()
|
||||
.blockFirst();
|
||||
Assertions.assertThat(summary).isNotNull();
|
||||
Assertions.assertThat(summary.getId()).isNotEmpty();
|
||||
Assertions.assertThat(summary.getName()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void instances() throws Exception {
|
||||
|
||||
Assume.assumeTrue(configExists());
|
||||
|
||||
Log log = LogFactory.getLog(getClass());
|
||||
ApplicationContext ctx = new SpringApplicationBuilder()
|
||||
.sources(MyConfig.class)
|
||||
.properties(defaultConfig())
|
||||
.run();
|
||||
|
||||
CloudFoundryOperations cf = ctx.getBean(CloudFoundryOperations.class);
|
||||
Flux<Tuple2<ApplicationDetail, InstanceDetail>> mapMany = cf
|
||||
.applications()
|
||||
.get(GetApplicationRequest.builder().name("lo-test").build())
|
||||
.flatMapMany(applicationDetail -> {
|
||||
List<InstanceDetail> instanceDetails = applicationDetail.getInstanceDetails();
|
||||
Flux<InstanceDetail> ids = Flux.fromStream(instanceDetails.stream());
|
||||
Flux<ApplicationDetail> generate = Flux.generate(sink -> sink.next(applicationDetail));
|
||||
return generate.zipWith(ids);
|
||||
});
|
||||
mapMany
|
||||
.subscribe(p ->
|
||||
log.info(p.getT1().getName() + ':' + p.getT1().getId() + ':' + p.getT2().getIndex() + ':' +
|
||||
p.getT2().getState()));
|
||||
|
||||
Thread.sleep(5 * 1000);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +1,100 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-cloudfoundry-dependencies</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-cloudfoundry-dependencies</name>
|
||||
<description>Spring Cloud Cloudfoundry Dependencies</description>
|
||||
<properties>
|
||||
<cloudfoundry-client-lib.version>1.1.3</cloudfoundry-client-lib.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-discovery</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-web</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-cloudfoundry</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cloudfoundry</groupId>
|
||||
<artifactId>cloudfoundry-client-lib</artifactId>
|
||||
<version>${cloudfoundry-client-lib.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-cloudfoundry-dependencies</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-cloudfoundry-dependencies</name>
|
||||
<description>Spring Cloud Cloudfoundry Dependencies</description>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-discovery</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-web</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-cloudfoundry</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-cloudfoundry-discovery</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<description>Spring Cloud CloudFoundry Discovery</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
@@ -28,10 +30,6 @@
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cloudfoundry</groupId>
|
||||
<artifactId>cloudfoundry-client-lib</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-core</artifactId>
|
||||
@@ -48,5 +46,4 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
@@ -16,58 +16,22 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.cloudfoundry.client.lib.CloudCredentials;
|
||||
import org.cloudfoundry.client.lib.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.lib.domain.CloudApplication;
|
||||
import org.cloudfoundry.client.lib.domain.InstanceInfo;
|
||||
import org.cloudfoundry.client.lib.domain.InstanceState;
|
||||
import org.cloudfoundry.client.lib.domain.InstancesInfo;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.ApplicationSummary;
|
||||
import org.cloudfoundry.operations.applications.InstanceDetail;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.cloud.cloudfoundry.CloudFoundryService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Cloud Foundry v2 API-aware implementation of the {@link DiscoveryClient discovery
|
||||
* client} SPI. Cloud Foundry already retains a registry of running applications which we
|
||||
* expose here as services. Newer versions of Cloud Foundry support instance-specific
|
||||
* networking, but as the Cloud Foundry client API doesn't yet support that, this
|
||||
* {@link DiscoveryClient implementation doesn't either}.
|
||||
* <p/>
|
||||
* You need to provide an instance of the {@link CloudFoundryClient}. A workable
|
||||
* configuration looks like this:
|
||||
* <p/>
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* CloudFoundryClient cloudFoundryClient(
|
||||
* @Value("${MY_CUSTOM_CF_API:https://api.run.pivotal.io}") String api,
|
||||
* CloudCredentials cc) throws MalformedURLException {
|
||||
* CloudFoundryClient cloudFoundryClient = new CloudFoundryClient(cc,
|
||||
* URI.create(api).toURL());
|
||||
* cloudFoundryClient.login();
|
||||
* return cloudFoundryClient;
|
||||
* }
|
||||
* </pre>
|
||||
* <p/>
|
||||
* You can configure all sorts of other things including which Cloud Foundry cloud
|
||||
* controller URI to use, how and whether to use an HTTP proxy, and more using alternative
|
||||
* constructors. As configured above, the client will talk to all services and
|
||||
* applications deployed in <EM>all</EM> spaces and organizations. Use one of the
|
||||
* {@link CloudFoundryClient#CloudFoundryClient(CloudCredentials, URL, String, String)}
|
||||
* variants to specify which space and organization to use.
|
||||
* <p/>
|
||||
* Cloud Foundry maintains a registry of running applications which we expose here as CloudFoundryService instances.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Spencer Gibb
|
||||
@@ -75,119 +39,60 @@ import org.springframework.core.env.Environment;
|
||||
*/
|
||||
public class CloudFoundryDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
private static final String DESCRIPTION = "Cloud Foundry "
|
||||
+ DiscoveryClient.class.getName() + " implementation";
|
||||
private final CloudFoundryService cloudFoundryService;
|
||||
private final CloudFoundryOperations cloudFoundryOperations;
|
||||
|
||||
private static final Log log = LogFactory.getLog(CloudFoundryDiscoveryClient.class);
|
||||
|
||||
private final CloudFoundryClient cloudFoundryClient;
|
||||
private final String description = "Cloud Foundry " + DiscoveryClient.class.getName() + " implementation";
|
||||
|
||||
@Value("${vcap.application.name:${spring.application.name:application}}")
|
||||
private String vcapApplicationName = "application";
|
||||
|
||||
public CloudFoundryDiscoveryClient(CloudFoundryClient cloudFoundryClient,
|
||||
Environment environment) {
|
||||
this.cloudFoundryClient = cloudFoundryClient;
|
||||
|
||||
CloudFoundryDiscoveryClient(CloudFoundryOperations cloudFoundryOperations,
|
||||
CloudFoundryService svc) {
|
||||
this.cloudFoundryService = svc;
|
||||
this.cloudFoundryOperations = cloudFoundryOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
|
||||
public ServiceInstance getLocalServiceInstance() {
|
||||
List<ServiceInstance> serviceInstances = null;
|
||||
try {
|
||||
CloudApplication application = this.cloudFoundryClient
|
||||
.getApplication(this.vcapApplicationName);
|
||||
serviceInstances = this.createServiceInstancesFromCloudApplications(
|
||||
Collections.singletonList(application));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Could not determine local service instance: " + e.getClass() + " ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
return serviceInstances != null && serviceInstances.size() > 0
|
||||
? serviceInstances.iterator().next() : null;
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> getInstances(String s) {
|
||||
try {
|
||||
CloudApplication applications = this.cloudFoundryClient.getApplication(s);
|
||||
return this.createServiceInstancesFromCloudApplications(
|
||||
Collections.singletonList(applications));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Could not get service instances: " + e.getClass() + " ("
|
||||
+ e.getMessage() + ")");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
public List<ServiceInstance> getInstances(String serviceId) {
|
||||
return cloudFoundryService
|
||||
.getApplicationInstances(serviceId)
|
||||
.map(tuple -> {
|
||||
ApplicationDetail applicationDetail = tuple.getT1();
|
||||
InstanceDetail instanceDetail = tuple.getT2();
|
||||
|
||||
private boolean isRunning(CloudApplication ca) {
|
||||
InstancesInfo ii = this.cloudFoundryClient.getApplicationInstances(ca);
|
||||
List<InstanceInfo> instances;
|
||||
if (ii != null && (instances = ii.getInstances()) != null) {
|
||||
for (InstanceInfo resolved : instances) {
|
||||
InstanceState state = resolved.getState();
|
||||
if (state != null && state.equals(InstanceState.RUNNING)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
String applicationId = applicationDetail.getId();
|
||||
String applicationIndex = instanceDetail.getIndex();
|
||||
String name = applicationDetail.getName();
|
||||
String url = applicationDetail.getUrls().size() > 0 ? applicationDetail.getUrls().get(0) : null;
|
||||
boolean secure = (url + "").toLowerCase().startsWith("https");
|
||||
|
||||
HashMap<String, String> metadata = new HashMap<>();
|
||||
metadata.put("applicationId", applicationId);
|
||||
metadata.put("instanceId", applicationIndex);
|
||||
|
||||
return (ServiceInstance) new DefaultServiceInstance(name, url, 80, secure, metadata);
|
||||
})
|
||||
.collectList()
|
||||
.blockOptional()
|
||||
.orElse(new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getServices() {
|
||||
List<String> services = new ArrayList<>();
|
||||
List<CloudApplication> applications;
|
||||
try {
|
||||
applications = this.cloudFoundryClient.getApplications();
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Could not get applications: " + e.getClass() + " ("
|
||||
+ e.getMessage() + ")");
|
||||
applications = Collections.emptyList();
|
||||
}
|
||||
Set<String> serviceIds = new HashSet<>();
|
||||
for (CloudApplication ca : applications) {
|
||||
if (isRunning(ca)) {
|
||||
serviceIds.add(ca.getName());
|
||||
}
|
||||
}
|
||||
services.addAll(serviceIds);
|
||||
return services;
|
||||
}
|
||||
|
||||
protected List<ServiceInstance> createServiceInstancesFromCloudApplications(
|
||||
Collection<CloudApplication> cloudApplications) {
|
||||
Set<ServiceInstance> serviceInstances = new HashSet<>();
|
||||
for (CloudApplication ca : cloudApplications) {
|
||||
if (isRunning(ca)) {
|
||||
serviceInstances.add(new CloudFoundryServiceInstance(ca));
|
||||
}
|
||||
}
|
||||
List<ServiceInstance> instances = new ArrayList<>();
|
||||
instances.addAll(serviceInstances);
|
||||
return instances;
|
||||
}
|
||||
|
||||
public static class CloudFoundryServiceInstance extends DefaultServiceInstance {
|
||||
|
||||
private final CloudApplication cloudApplication;
|
||||
|
||||
public CloudApplication getCloudApplication() {
|
||||
return this.cloudApplication;
|
||||
}
|
||||
|
||||
public CloudFoundryServiceInstance(CloudApplication ca) {
|
||||
super(ca.getName(),
|
||||
ca.getUris().isEmpty() ? "localhost" : ca.getUris().iterator().next(),
|
||||
80, false);
|
||||
|
||||
this.cloudApplication = ca;
|
||||
}
|
||||
return this
|
||||
.cloudFoundryOperations
|
||||
.applications()
|
||||
.list()
|
||||
.map(ApplicationSummary::getName)
|
||||
.collectList()
|
||||
.blockOptional()
|
||||
.orElse(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,22 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.cloudfoundry.client.lib.CloudCredentials;
|
||||
import org.cloudfoundry.client.lib.CloudFoundryClient;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.cloudfoundry.CloudFoundryService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Josh Long
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(CloudFoundryClient.class)
|
||||
@ConditionalOnClass(CloudFoundryOperations.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.cloudfoundry.discovery.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(CloudFoundryDiscoveryProperties.class)
|
||||
public class CloudFoundryDiscoveryClientConfiguration {
|
||||
@@ -43,41 +39,15 @@ public class CloudFoundryDiscoveryClientConfiguration {
|
||||
@Autowired
|
||||
private CloudFoundryDiscoveryProperties discovery;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CloudCredentials.class)
|
||||
public CloudCredentials cloudCredentials() {
|
||||
return new CloudCredentials(this.discovery.getUsername(),
|
||||
this.discovery.getPassword());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CloudFoundryClient.class)
|
||||
public CloudFoundryClient cloudFoundryClient(CloudCredentials cc)
|
||||
throws MalformedURLException {
|
||||
CloudFoundryClient cloudFoundryClient;
|
||||
if (StringUtils.hasText(this.discovery.getOrg()) && StringUtils.hasText(this.discovery.getSpace())) {
|
||||
cloudFoundryClient = new CloudFoundryClient(cc,
|
||||
URI.create(this.discovery.getUrl()).toURL(), this.discovery.getOrg(),
|
||||
this.discovery.getSpace());
|
||||
}
|
||||
else {
|
||||
cloudFoundryClient = new CloudFoundryClient(cc,
|
||||
URI.create(this.discovery.getUrl()).toURL());
|
||||
}
|
||||
cloudFoundryClient.login();
|
||||
return cloudFoundryClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CloudFoundryDiscoveryClient.class)
|
||||
public CloudFoundryDiscoveryClient cloudFoundryDiscoveryClient(
|
||||
CloudFoundryClient cloudFoundryClient, Environment environment) {
|
||||
return new CloudFoundryDiscoveryClient(cloudFoundryClient, environment);
|
||||
CloudFoundryOperations cf, CloudFoundryService svc) {
|
||||
return new CloudFoundryDiscoveryClient(cf, svc);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CloudFoundryHeartbeatSender cloudFoundryHeartbeatSender(CloudFoundryDiscoveryClient client) {
|
||||
return new CloudFoundryHeartbeatSender(client);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,32 +25,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@ConfigurationProperties(prefix = "spring.cloud.cloudfoundry.discovery")
|
||||
public class CloudFoundryDiscoveryProperties {
|
||||
|
||||
/**
|
||||
* URL of Cloud Foundry API (Cloud Controller).
|
||||
*/
|
||||
private String url = "https://api.run.pivotal.io";
|
||||
|
||||
/**
|
||||
* Username to authenticate (usually an email address).
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Password for user to authenticate and obtain token.
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Organization name to authenticate with (default to user's default).
|
||||
*/
|
||||
private String org;
|
||||
|
||||
/**
|
||||
* Space name to authenticate with (default to user's default).
|
||||
*/
|
||||
@Value("${vcap.application.space_name:}")
|
||||
private String space;
|
||||
|
||||
/**
|
||||
* Flag to indicate that discovery is enabled.
|
||||
*/
|
||||
@@ -63,53 +37,13 @@ public class CloudFoundryDiscoveryProperties {
|
||||
private long heartbeatFrequency = 5000;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
public void setUrl(String cloudControllerUrl) {
|
||||
this.url = cloudControllerUrl;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String email) {
|
||||
this.username = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getOrg() {
|
||||
return this.org;
|
||||
}
|
||||
|
||||
public void setOrg(String org) {
|
||||
this.org = org;
|
||||
}
|
||||
|
||||
public String getSpace() {
|
||||
return this.space;
|
||||
}
|
||||
|
||||
public void setSpace(String space) {
|
||||
this.space = space;
|
||||
}
|
||||
|
||||
public long getHeartbeatFrequency() {
|
||||
return this.heartbeatFrequency;
|
||||
}
|
||||
|
||||
@@ -16,20 +16,19 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.cloudfoundry.client.lib.CloudFoundryClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.config.DynamicStringProperty;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.cloudfoundry.CloudFoundryService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @author Josh Long
|
||||
@@ -52,10 +51,9 @@ public class CloudFoundryRibbonClientConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ServerList<?> ribbonServerList(CloudFoundryClient cloudFoundryClient,
|
||||
IClientConfig config) {
|
||||
CloudFoundryServerList cloudFoundryServerList = new CloudFoundryServerList(
|
||||
cloudFoundryClient);
|
||||
public ServerList<?> ribbonServerList(CloudFoundryService svc,
|
||||
IClientConfig config) {
|
||||
CloudFoundryServerList cloudFoundryServerList = new CloudFoundryServerList(svc);
|
||||
cloudFoundryServerList.initWithNiwsConfig(config);
|
||||
return cloudFoundryServerList;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import org.cloudfoundry.client.lib.domain.CloudApplication;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
/**
|
||||
@@ -27,14 +25,12 @@ public class CloudFoundryServer extends Server {
|
||||
|
||||
private final MetaInfo metaInfo;
|
||||
|
||||
public CloudFoundryServer(final CloudApplication cloudApplication) {
|
||||
|
||||
super(cloudApplication.getUris().iterator().next(), 80);
|
||||
|
||||
public CloudFoundryServer(String appName, String uri, int port) {
|
||||
super(uri, port);
|
||||
this.metaInfo = new MetaInfo() {
|
||||
@Override
|
||||
public String getAppName() {
|
||||
return cloudApplication.getName();
|
||||
return appName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,12 +40,12 @@ public class CloudFoundryServer extends Server {
|
||||
|
||||
@Override
|
||||
public String getServiceIdForDiscovery() {
|
||||
return cloudApplication.getName();
|
||||
return appName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInstanceId() {
|
||||
return cloudApplication.getName();
|
||||
return appName;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,30 +16,24 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.cloudfoundry.client.lib.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.lib.domain.CloudApplication;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.AbstractServerList;
|
||||
import org.springframework.cloud.cloudfoundry.CloudFoundryService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Josh Long
|
||||
*/
|
||||
public class CloudFoundryServerList extends AbstractServerList<CloudFoundryServer> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(CloudFoundryServerList.class);
|
||||
private String serviceId;
|
||||
|
||||
protected String serviceId;
|
||||
private final CloudFoundryService cloudFoundryService;
|
||||
|
||||
private final CloudFoundryClient cloudFoundryClient;
|
||||
|
||||
public CloudFoundryServerList(CloudFoundryClient cloudFoundryClient) {
|
||||
this.cloudFoundryClient = cloudFoundryClient;
|
||||
CloudFoundryServerList(CloudFoundryService svc) {
|
||||
this.cloudFoundryService = svc;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,15 +51,12 @@ public class CloudFoundryServerList extends AbstractServerList<CloudFoundryServe
|
||||
return this.cloudFoundryServers();
|
||||
}
|
||||
|
||||
protected List<CloudFoundryServer> cloudFoundryServers() {
|
||||
try {
|
||||
CloudApplication cloudApplications = this.cloudFoundryClient
|
||||
.getApplication(this.serviceId);
|
||||
return Collections.singletonList(new CloudFoundryServer(cloudApplications));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Cannot determine server list for " + this.serviceId + ": " + e.getClass() + "(" + e.getMessage() + ")");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
private List<CloudFoundryServer> cloudFoundryServers() {
|
||||
return cloudFoundryService
|
||||
.getApplicationInstances(this.serviceId)
|
||||
.map(tpl -> new CloudFoundryServer(tpl.getT1().getName(), tpl.getT1().getUrls().get(0), 80))
|
||||
.collectList()
|
||||
.blockOptional()
|
||||
.orElse(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses({ CloudFoundryServerListTest.class, CloudFoundryAutoConfigurationTest.class,
|
||||
CloudFoundryServerTest.class, CloudFoundryDiscoveryClientTest.class })
|
||||
@SuiteClasses({CloudFoundryServerListTest.class})
|
||||
@Ignore
|
||||
public class AdhocTestSuite {
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or 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.cloudfoundry.discovery;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.cloudfoundry.client.lib.CloudCredentials;
|
||||
import org.cloudfoundry.client.lib.CloudFoundryClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author <A href= "josh@joshlong.com">Josh Long</A>
|
||||
*/
|
||||
public class CloudFoundryAutoConfigurationTest {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
String hiServiceServiceId = "foo-service";
|
||||
|
||||
Object vcapAppl = "{\"limits\":{\"mem\":1024,\"disk\":1024,\"fds\":16384},\"application_version\":"
|
||||
+ "\"36eff082-96d6-498f-8214-508fda72ba65\",\"application_name\":\""
|
||||
+ hiServiceServiceId
|
||||
+ "\",\"application_uris\""
|
||||
+ ":[\""
|
||||
+ hiServiceServiceId
|
||||
+ ".cfapps.io\"],\"version\":\"36eff082-96d6-498f-8214-508fda72ba65\",\"name\":"
|
||||
+ "\"hi-service\",\"space_name\":\"joshlong\",\"space_id\":\"e0cd969c-3461-41ae-abde-4e11bb5acbd1\","
|
||||
+ "\"uris\":[\"hi-service.cfapps.io\"],\"users\":null,\"application_id\":\"af350f7c-88c4-4e35-a04e-698a1dbc7354\","
|
||||
+ "\"instance_id\":\"e4843ca23bd947b28e6d4cb3f9b92cbb\",\"instance_index\":0,\"host\":\"0.0.0.0\",\"port\":61590,"
|
||||
+ "\"started_at\":\"2015-05-07 20:00:10 +0000\",\"started_at_timestamp\":1431028810,\"start\":\"2015-05-07 20:00:10 +0000\","
|
||||
+ "\"state_timestamp\":1431028810}";
|
||||
|
||||
this.context = new SpringApplicationBuilder()
|
||||
.properties("VCAP_APPLICATION:"+vcapAppl, "server.port=0")
|
||||
.sources(SimpleConfiguration.class).run();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Throwable {
|
||||
synchronized (this) {
|
||||
if (null != this.context)
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
@EnableAutoConfiguration
|
||||
public static class SimpleConfiguration {
|
||||
|
||||
@Bean
|
||||
CloudCredentials cloudCredentials() {
|
||||
return Mockito.mock(CloudCredentials.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
CloudFoundryClient cloudFoundryClient() {
|
||||
return Mockito.mock(CloudFoundryClient.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextLoaded() {
|
||||
LogFactory.getLog(getClass()).debug("contextLoad()");
|
||||
Assert.assertTrue(this.context.getBeansOfType(CloudFoundryDiscoveryClient.class)
|
||||
.size() > 0);
|
||||
Assert.assertTrue(this.context.getBeansOfType(
|
||||
CloudFoundryDiscoveryProperties.class).size() > 0);
|
||||
Assert.assertTrue(this.context.getBeansOfType(CloudFoundryClient.class).size() > 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,31 +16,28 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.cloudfoundry.client.lib.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.lib.CloudFoundryException;
|
||||
import org.cloudfoundry.client.lib.domain.CloudApplication;
|
||||
import org.cloudfoundry.client.lib.domain.InstanceInfo;
|
||||
import org.cloudfoundry.client.lib.domain.InstanceState;
|
||||
import org.cloudfoundry.client.lib.domain.InstancesInfo;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.ApplicationSummary;
|
||||
import org.cloudfoundry.operations.applications.Applications;
|
||||
import org.cloudfoundry.operations.applications.InstanceDetail;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.cloud.cloudfoundry.CloudFoundryService;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author <A href="mailto:josh@Joshlong.com">Josh Long</A>
|
||||
@@ -48,144 +45,59 @@ import org.springframework.http.HttpStatus;
|
||||
public class CloudFoundryDiscoveryClientTest {
|
||||
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private CloudFoundryDiscoveryClient cloudFoundryDiscoveryClient;
|
||||
|
||||
private CloudApplication cloudApplication;
|
||||
|
||||
private String hiServiceServiceId = "hi-service";
|
||||
|
||||
private CloudFoundryClient cloudFoundryClient;
|
||||
|
||||
private CloudApplication fakeCloudApplication(String name, String... uri) {
|
||||
CloudApplication cloudApplication = mock(CloudApplication.class);
|
||||
given(cloudApplication.getName()).willReturn(name);
|
||||
given(cloudApplication.getUris()).willReturn(Arrays.asList(uri));
|
||||
return cloudApplication;
|
||||
}
|
||||
private CloudFoundryOperations ops;
|
||||
private CloudFoundryService svc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.cloudFoundryClient = mock(CloudFoundryClient.class);
|
||||
Environment environment = mock(Environment.class);
|
||||
|
||||
given(environment.getProperty("VCAP_APPLICATION"))
|
||||
.willReturn(
|
||||
"{\"limits\":{\"mem\":1024,\"disk\":1024,\"fds\":16384},\"application_version\":"
|
||||
+ "\"36eff082-96d6-498f-8214-508fda72ba65\",\"application_name\":\""
|
||||
+ this.hiServiceServiceId
|
||||
+ "\",\"application_uris\""
|
||||
+ ":[\""
|
||||
+ this.hiServiceServiceId
|
||||
+ ".cfapps.io\"],\"version\":\"36eff082-96d6-498f-8214-508fda72ba65\",\"name\":"
|
||||
+ "\"hi-service\",\"space_name\":\"joshlong\",\"space_id\":\"e0cd969c-3461-41ae-abde-4e11bb5acbd1\","
|
||||
+ "\"uris\":[\"hi-service.cfapps.io\"],\"users\":null,\"application_id\":\"af350f7c-88c4-4e35-a04e-698a1dbc7354\","
|
||||
+ "\"instance_id\":\"e4843ca23bd947b28e6d4cb3f9b92cbb\",\"instance_index\":0,\"host\":\"0.0.0.0\",\"port\":61590,"
|
||||
+ "\"started_at\":\"2015-05-07 20:00:10 +0000\",\"started_at_timestamp\":1431028810,\"start\":\"2015-05-07 20:00:10 +0000\","
|
||||
+ "\"state_timestamp\":1431028810}");
|
||||
|
||||
List<CloudApplication> cloudApplications = new ArrayList<>();
|
||||
cloudApplications.add(fakeCloudApplication(this.hiServiceServiceId,
|
||||
"hi-service.cfapps.io", "hi-service-1.cfapps.io"));
|
||||
cloudApplications.add(fakeCloudApplication("config-service",
|
||||
"conf-service.cfapps.io", "conf-service-1.cfapps.io"));
|
||||
|
||||
given(this.cloudFoundryClient.getApplications()).willReturn(cloudApplications);
|
||||
|
||||
this.cloudApplication = cloudApplications.get(0);
|
||||
given(this.cloudFoundryClient.getApplication(this.hiServiceServiceId))
|
||||
.willReturn(this.cloudApplication);
|
||||
|
||||
given(this.cloudFoundryClient.getApplication(this.hiServiceServiceId))
|
||||
.willReturn(this.cloudApplication);
|
||||
|
||||
InstanceInfo instanceInfo = mock(InstanceInfo.class);
|
||||
InstancesInfo instancesInfo = mock(InstancesInfo.class);
|
||||
given(instancesInfo.getInstances()).willReturn(
|
||||
Collections.singletonList(instanceInfo));
|
||||
given(instanceInfo.getState()).willReturn(InstanceState.RUNNING);
|
||||
|
||||
given(this.cloudFoundryClient.getApplicationInstances(this.cloudApplication))
|
||||
.willReturn(instancesInfo);
|
||||
|
||||
this.cloudFoundryDiscoveryClient = new CloudFoundryDiscoveryClient(
|
||||
this.cloudFoundryClient, environment);
|
||||
this.ops = mock(CloudFoundryOperations.class);
|
||||
this.svc = mock(CloudFoundryService.class);
|
||||
this.cloudFoundryDiscoveryClient = new CloudFoundryDiscoveryClient(this.ops, this.svc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceResolution() {
|
||||
Applications apps = mock(Applications.class);
|
||||
ApplicationSummary s = ApplicationSummary.builder()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.instances(2)
|
||||
.memoryLimit(1024)
|
||||
.requestedState("requestedState")
|
||||
.diskQuota(1024)
|
||||
.name(this.hiServiceServiceId)
|
||||
.runningInstances(2)
|
||||
.build();
|
||||
Mockito.when(apps.list()).thenReturn(Flux.just(s));
|
||||
Mockito.when(this.ops.applications()).thenReturn(apps);
|
||||
List<String> serviceNames = this.cloudFoundryDiscoveryClient.getServices();
|
||||
|
||||
Assert.assertTrue("there should be one registered service.",
|
||||
serviceNames.contains(this.hiServiceServiceId));
|
||||
|
||||
for (String serviceName : serviceNames) {
|
||||
this.log.debug("\t discovered serviceName: " + serviceName);
|
||||
}
|
||||
Assert.assertTrue("there should be one registered service.", serviceNames.contains(this.hiServiceServiceId));
|
||||
serviceNames.forEach(serviceName -> this.log.debug("\t discovered serviceName: " + serviceName));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstances() {
|
||||
ApplicationDetail applicationDetail = ApplicationDetail
|
||||
.builder()
|
||||
.instances(2)
|
||||
.name("my-app")
|
||||
.stack("stack")
|
||||
.memoryLimit(1024)
|
||||
.id("id")
|
||||
.requestedState("requestedState")
|
||||
.runningInstances(2)
|
||||
.url("http://my-app.cfapps.io")
|
||||
.diskQuota(20)
|
||||
.build();
|
||||
InstanceDetail instanceDetail = InstanceDetail
|
||||
.builder()
|
||||
.index("0")
|
||||
.build();
|
||||
Tuple2<ApplicationDetail, InstanceDetail> tuple2 = Tuples.of(applicationDetail, instanceDetail);
|
||||
Mockito.when(svc.getApplicationInstances(this.hiServiceServiceId)).thenReturn(Flux.just(tuple2));
|
||||
List<ServiceInstance> instances = this.cloudFoundryDiscoveryClient
|
||||
.getInstances(this.hiServiceServiceId);
|
||||
assertEquals("Wrong instances: " + instances, 1, instances.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstancesNotAvailable() {
|
||||
given(this.cloudFoundryClient.getApplication(this.hiServiceServiceId)).willThrow(new RuntimeException("Planned"));
|
||||
List<ServiceInstance> instances = this.cloudFoundryDiscoveryClient
|
||||
.getInstances(this.hiServiceServiceId);
|
||||
assertEquals("Wrong instances: " + instances, 0, instances.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalServiceInstanceRunning() {
|
||||
|
||||
given(this.cloudFoundryClient.getApplication("application"))
|
||||
.willReturn(this.cloudApplication);
|
||||
InstanceInfo instanceInfo = mock(InstanceInfo.class);
|
||||
InstancesInfo instancesInfo = mock(InstancesInfo.class);
|
||||
given(instancesInfo.getInstances()).willReturn(
|
||||
Collections.singletonList(instanceInfo));
|
||||
given(instanceInfo.getState()).willReturn(InstanceState.RUNNING);
|
||||
|
||||
given(this.cloudFoundryClient.getApplicationInstances(this.cloudApplication))
|
||||
.willReturn(instancesInfo);
|
||||
|
||||
ServiceInstance localServiceInstance = this.cloudFoundryDiscoveryClient
|
||||
.getLocalServiceInstance();
|
||||
assertTrue(localServiceInstance.getHost().contains("hi-service.cfapps.io"));
|
||||
assertTrue(localServiceInstance.getServiceId().equals(this.hiServiceServiceId));
|
||||
assertEquals(localServiceInstance.getPort(), 80);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalServiceInstanceNotRunning() {
|
||||
|
||||
InstanceInfo instanceInfo = mock(InstanceInfo.class);
|
||||
InstancesInfo instancesInfo = mock(InstancesInfo.class);
|
||||
given(instancesInfo.getInstances()).willReturn(
|
||||
Collections.singletonList(instanceInfo));
|
||||
given(instanceInfo.getState()).willReturn(InstanceState.CRASHED);
|
||||
|
||||
given(this.cloudFoundryClient.getApplicationInstances(this.cloudApplication))
|
||||
.willReturn(instancesInfo);
|
||||
|
||||
ServiceInstance localServiceInstance = this.cloudFoundryDiscoveryClient
|
||||
.getLocalServiceInstance();
|
||||
assertNull(localServiceInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalServiceInstanceNotFound() {
|
||||
|
||||
given(this.cloudFoundryClient.getApplicationInstances(this.cloudApplication))
|
||||
.willThrow(new CloudFoundryException(HttpStatus.NOT_FOUND));
|
||||
|
||||
ServiceInstance localServiceInstance = this.cloudFoundryDiscoveryClient
|
||||
.getLocalServiceInstance();
|
||||
assertNull(localServiceInstance);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,22 +17,24 @@
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
|
||||
import org.cloudfoundry.client.lib.CloudFoundryClient;
|
||||
import org.cloudfoundry.client.lib.CloudFoundryException;
|
||||
import org.cloudfoundry.client.lib.domain.CloudApplication;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.InstanceDetail;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.cloud.cloudfoundry.CloudFoundryService;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author <A href="mailto:josh@Joshlong.com">Josh Long</A>
|
||||
@@ -41,52 +43,53 @@ public class CloudFoundryServerListTest {
|
||||
|
||||
private CloudFoundryServerList cloudFoundryServerList;
|
||||
private String serviceId = "foo-service";
|
||||
private CloudFoundryClient cloudFoundryClient;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
CloudApplication cloudApplication = mock(CloudApplication.class);
|
||||
given(cloudApplication.getUris()).will(new Answer<List<String>>() {
|
||||
@Override
|
||||
public List<String> answer(InvocationOnMock invocationOnMock)
|
||||
throws Throwable {
|
||||
return Arrays.asList("a-url.com", "b-url.com");
|
||||
}
|
||||
});
|
||||
|
||||
cloudFoundryClient = mock(CloudFoundryClient.class);
|
||||
given(cloudFoundryClient.getApplication(this.serviceId)).willReturn(
|
||||
cloudApplication);
|
||||
|
||||
IClientConfig iClientConfig = mock(IClientConfig.class);
|
||||
given(iClientConfig.getClientName()).willReturn(this.serviceId);
|
||||
when(iClientConfig.getClientName()).thenReturn(this.serviceId);
|
||||
|
||||
this.cloudFoundryServerList = new CloudFoundryServerList(cloudFoundryClient);
|
||||
CloudFoundryService cfs = mock(CloudFoundryService.class);
|
||||
ApplicationDetail applicationDetail = ApplicationDetail
|
||||
.builder()
|
||||
.instances(2)
|
||||
.name("my-app")
|
||||
.stack("stack")
|
||||
.memoryLimit(1024)
|
||||
.id("id")
|
||||
.requestedState("requestedState")
|
||||
.runningInstances(2)
|
||||
.url("http://my-app.cfapps.io")
|
||||
.diskQuota(20)
|
||||
.build();
|
||||
|
||||
InstanceDetail instanceDetail = InstanceDetail
|
||||
.builder()
|
||||
.index("0")
|
||||
.build();
|
||||
|
||||
Tuple2<ApplicationDetail, InstanceDetail> tuple2 = Tuples.of(applicationDetail, instanceDetail);
|
||||
Mockito.when(cfs.getApplicationInstances(this.serviceId)).thenReturn(Flux.just(tuple2));
|
||||
|
||||
this.cloudFoundryServerList = new CloudFoundryServerList(cfs);
|
||||
this.cloudFoundryServerList.initWithNiwsConfig(iClientConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListOfServers() {
|
||||
List<CloudFoundryServer> initialListOfServers = this.cloudFoundryServerList
|
||||
.getInitialListOfServers();
|
||||
List<CloudFoundryServer> updatedListOfServers = this.cloudFoundryServerList
|
||||
.getUpdatedListOfServers();
|
||||
List<CloudFoundryServer> initialListOfServers = this.cloudFoundryServerList.getInitialListOfServers();
|
||||
List<CloudFoundryServer> updatedListOfServers = this.cloudFoundryServerList.getUpdatedListOfServers();
|
||||
Assert.assertEquals(updatedListOfServers, initialListOfServers);
|
||||
Assert.assertTrue(initialListOfServers.size() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListOfServersFails() {
|
||||
given(cloudFoundryClient.getApplication(this.serviceId)).willThrow(
|
||||
new CloudFoundryException(HttpStatus.NOT_FOUND));
|
||||
List<CloudFoundryServer> initialListOfServers = this.cloudFoundryServerList
|
||||
.getInitialListOfServers();
|
||||
Assert.assertTrue(initialListOfServers.size() == 0);
|
||||
public void testInit() throws Exception {
|
||||
Field field = ReflectionUtils.findField(this.cloudFoundryServerList.getClass(), "serviceId");
|
||||
assert field != null;
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
Assert.assertEquals(String.class.cast(field.get(this.cloudFoundryServerList)), this.serviceId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInit() {
|
||||
Assert.assertEquals(this.cloudFoundryServerList.serviceId, this.serviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or 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.cloudfoundry.discovery;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.cloudfoundry.client.lib.domain.CloudApplication;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
|
||||
/**
|
||||
* @author <A href="mailto:josh@Joshlong.com">Josh Long</A>
|
||||
*/
|
||||
public class CloudFoundryServerTest {
|
||||
|
||||
private CloudFoundryServer cloudFoundryServer;
|
||||
private List<String> urls = Arrays.asList("a-url.com", "b-url.com");
|
||||
private String serverName = "server-name";
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
CloudApplication cloudApplication = mock(CloudApplication.class);
|
||||
given(cloudApplication.getUris()).willReturn(this.urls);
|
||||
given(cloudApplication.getName()).willReturn(this.serverName);
|
||||
given(cloudApplication.getRunningInstances()).willReturn(1);
|
||||
this.cloudFoundryServer = new CloudFoundryServer(cloudApplication);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProperConstruction() {
|
||||
Server.MetaInfo metaInfo = this.cloudFoundryServer.getMetaInfo();
|
||||
|
||||
Assert.assertEquals(metaInfo.getAppName(), this.serverName);
|
||||
Assert.assertEquals(metaInfo.getServiceIdForDiscovery(), this.serverName);
|
||||
Assert.assertEquals(metaInfo.getInstanceId(), this.serverName);
|
||||
Assert.assertEquals(this.cloudFoundryServer.getHost(), this.urls.get(0));
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry.sample;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.cloudfoundry.discovery.CloudFoundryDiscoveryClient;
|
||||
import org.springframework.cloud.cloudfoundry.discovery.EnableCloudFoundryClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -47,31 +44,13 @@ public class CloudFoundryApplication {
|
||||
SpringApplication.run(CloudFoundryApplication.class, args);
|
||||
}
|
||||
|
||||
private Log log = LogFactory.getLog(getClass());
|
||||
|
||||
@Bean
|
||||
CommandLineRunner consume(final CloudFoundryDiscoveryClient discoveryClient) {
|
||||
|
||||
return new CommandLineRunner() {
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
|
||||
// this demonstrates using the Spring Cloud Commons DiscoveryClient
|
||||
// abstraction
|
||||
log.info("=====================================");
|
||||
for (String svc : discoveryClient.getServices()) {
|
||||
CommandLineRunner demo(CloudFoundryDiscoveryClient discoveryClient) {
|
||||
Log log = LogFactory.getLog(getClass());
|
||||
return args ->
|
||||
discoveryClient.getServices().forEach(svc -> {
|
||||
log.info("service = " + svc);
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(svc);
|
||||
for (ServiceInstance si : instances) {
|
||||
log.info("\t" + si);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("=====================================");
|
||||
log.info("local: ");
|
||||
log.info("\t" + discoveryClient.getLocalServiceInstance());
|
||||
|
||||
}
|
||||
};
|
||||
discoveryClient.getInstances(svc).forEach(si -> log.info("\t" + si));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user