diff --git a/README.adoc b/README.adoc index 4863c61..a9b5f21 100644 --- a/README.adoc +++ b/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 diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index 96ebf97..0d5c979 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -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]). \ No newline at end of file +`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`. diff --git a/pom.xml b/pom.xml index a6ce50c..e9e14eb 100644 --- a/pom.xml +++ b/pom.xml @@ -12,14 +12,15 @@ spring-cloud-build 2.0.0.BUILD-SNAPSHOT - + - - cloudfoundry - ${basedir} - 2.0.0.BUILD-SNAPSHOT - 2.0.0.BUILD-SNAPSHOT - + + 3.3.0.RELEASE + cloudfoundry + ${basedir} + 2.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT + https://github.com/spring-cloud/spring-cloud-consul @@ -34,6 +35,7 @@ spring-cloud-cloudfoundry-dependencies + spring-cloud-cloudfoundry-commons spring-cloud-cloudfoundry-discovery spring-cloud-cloudfoundry-web spring-cloud-cloudfoundry-sample @@ -70,80 +72,80 @@ pom import - - org.springframework.cloud - spring-cloud-commons-dependencies - ${spring-cloud-commons.version} + + org.springframework.cloud + spring-cloud-commons-dependencies + ${spring-cloud-commons.version} pom import - - - commons-logging - commons-logging - 1.1.3 - - + + + commons-logging + commons-logging + 1.1.3 + + - - - spring - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/release - - false - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/libs-release-local - - false - - - - - + + + spring + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + false + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/release + + false + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/libs-release-local + + false + + + + + diff --git a/spring-cloud-cloudfoundry-commons/pom.xml b/spring-cloud-cloudfoundry-commons/pom.xml new file mode 100644 index 0000000..3f2aca7 --- /dev/null +++ b/spring-cloud-cloudfoundry-commons/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + spring-cloud-cloudfoundry-commons + jar + Spring Cloud CloudFoundry Commons + + org.springframework.cloud + spring-cloud-cloudfoundry + 2.0.0.BUILD-SNAPSHOT + .. + + + + org.cloudfoundry + cloudfoundry-client-reactor + ${cf-java-client.version} + + + org.cloudfoundry + cloudfoundry-operations + ${cf-java-client.version} + + + org.cloudfoundry + cloudfoundry-util + ${cf-java-client.version} + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-actuator + true + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfiguration.java b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfiguration.java new file mode 100644 index 0000000..8e569a8 --- /dev/null +++ b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfiguration.java @@ -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 Josh Long + * @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(); + } +} + + diff --git a/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryProperties.java b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryProperties.java new file mode 100644 index 0000000..f2072fb --- /dev/null +++ b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryProperties.java @@ -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 Josh Long + */ +@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 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))); + } +} \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryService.java b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryService.java new file mode 100644 index 0000000..dab7fc6 --- /dev/null +++ b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryService.java @@ -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 Josh Long + */ +public class CloudFoundryService { + + private final CloudFoundryOperations cloudFoundryOperations; + + public CloudFoundryService(CloudFoundryOperations cloudFoundryOperations) { + this.cloudFoundryOperations = cloudFoundryOperations; + } + + public Flux> getApplicationInstances(String serviceId) { + GetApplicationRequest applicationRequest = GetApplicationRequest.builder().name(serviceId).build(); + return this.cloudFoundryOperations + .applications() + .get(applicationRequest) + .flatMapMany(applicationDetail -> { + Flux ids = Flux.fromStream(applicationDetail.getInstanceDetails().stream()) + .filter(id -> id.getState().equalsIgnoreCase("RUNNING")); + Flux generate = Flux.generate(sink -> sink.next(applicationDetail)); + return generate.zipWith(ids); + }); + } + +} diff --git a/spring-cloud-cloudfoundry-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-cloudfoundry-commons/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..f4b6d26 --- /dev/null +++ b/spring-cloud-cloudfoundry-commons/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ + +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.cloudfoundry.CloudFoundryClientAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-commons/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfigurationTest.java b/spring-cloud-cloudfoundry-commons/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfigurationTest.java new file mode 100644 index 0000000..3284f8b --- /dev/null +++ b/spring-cloud-cloudfoundry-commons/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfigurationTest.java @@ -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 defaultConfig() { + Map 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> mapMany = cf + .applications() + .get(GetApplicationRequest.builder().name("lo-test").build()) + .flatMapMany(applicationDetail -> { + List instanceDetails = applicationDetail.getInstanceDetails(); + Flux ids = Flux.fromStream(instanceDetails.stream()); + Flux 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); + } + +} \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-dependencies/pom.xml b/spring-cloud-cloudfoundry-dependencies/pom.xml index 71ef7cc..d35c65b 100644 --- a/spring-cloud-cloudfoundry-dependencies/pom.xml +++ b/spring-cloud-cloudfoundry-dependencies/pom.xml @@ -1,98 +1,100 @@ - 4.0.0 - - spring-cloud-dependencies-parent - org.springframework.cloud - 2.0.0.BUILD-SNAPSHOT + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + spring-cloud-dependencies-parent + org.springframework.cloud + 2.0.0.BUILD-SNAPSHOT - - spring-cloud-cloudfoundry-dependencies - 2.0.0.BUILD-SNAPSHOT - pom - spring-cloud-cloudfoundry-dependencies - Spring Cloud Cloudfoundry Dependencies - - 1.1.3 - - - - - org.springframework.cloud - spring-cloud-cloudfoundry-discovery - ${project.version} - - - org.springframework.cloud - spring-cloud-cloudfoundry-web - ${project.version} - - - org.springframework.cloud - spring-cloud-starter-cloudfoundry - ${project.version} - - - org.cloudfoundry - cloudfoundry-client-lib - ${cloudfoundry-client-lib.version} - - - - - - spring - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/release - - false - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - - + + spring-cloud-cloudfoundry-dependencies + 2.0.0.BUILD-SNAPSHOT + pom + spring-cloud-cloudfoundry-dependencies + Spring Cloud Cloudfoundry Dependencies + + + + org.springframework.cloud + spring-cloud-cloudfoundry-discovery + ${project.version} + + + org.springframework.cloud + spring-cloud-cloudfoundry-commons + ${project.version} + + + org.springframework.cloud + spring-cloud-cloudfoundry-web + ${project.version} + + + org.springframework.cloud + spring-cloud-cloudfoundry-commons + ${project.version} + + + org.springframework.cloud + spring-cloud-starter-cloudfoundry + ${project.version} + + + + + + spring + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + false + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/release + + false + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + false + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + + diff --git a/spring-cloud-cloudfoundry-discovery/pom.xml b/spring-cloud-cloudfoundry-discovery/pom.xml index e721596..b7298e8 100644 --- a/spring-cloud-cloudfoundry-discovery/pom.xml +++ b/spring-cloud-cloudfoundry-discovery/pom.xml @@ -2,19 +2,21 @@ 4.0.0 - spring-cloud-cloudfoundry-discovery jar Spring Cloud CloudFoundry Discovery - org.springframework.cloud spring-cloud-cloudfoundry 2.0.0.BUILD-SNAPSHOT .. - + + org.springframework.cloud + spring-cloud-cloudfoundry-commons + ${project.version} + org.springframework.boot spring-boot-configuration-processor @@ -28,10 +30,6 @@ org.springframework.cloud spring-cloud-commons - - org.cloudfoundry - cloudfoundry-client-lib - org.springframework.cloud spring-cloud-netflix-core @@ -48,5 +46,4 @@ test - - + \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java index d75b2ea..526bf5e 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java @@ -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}. - *

- * You need to provide an instance of the {@link CloudFoundryClient}. A workable - * configuration looks like this: - *

- * - *

- * @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;
- * }
- * 
- *

- * 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 all spaces and organizations. Use one of the - * {@link CloudFoundryClient#CloudFoundryClient(CloudCredentials, URL, String, String)} - * variants to specify which space and organization to use. - *

+ * 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 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 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 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 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 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 getServices() { - List services = new ArrayList<>(); - List applications; - try { - applications = this.cloudFoundryClient.getApplications(); - } - catch (Exception e) { - log.warn("Could not get applications: " + e.getClass() + " (" - + e.getMessage() + ")"); - applications = Collections.emptyList(); - } - Set serviceIds = new HashSet<>(); - for (CloudApplication ca : applications) { - if (isRunning(ca)) { - serviceIds.add(ca.getName()); - } - } - services.addAll(serviceIds); - return services; - } - - protected List createServiceInstancesFromCloudApplications( - Collection cloudApplications) { - Set serviceInstances = new HashSet<>(); - for (CloudApplication ca : cloudApplications) { - if (isRunning(ca)) { - serviceInstances.add(new CloudFoundryServiceInstance(ca)); - } - } - List 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<>()); } } \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java index 272f033..1028684 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java @@ -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); } - } \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryProperties.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryProperties.java index fc1936f..f290f43 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryProperties.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryProperties.java @@ -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; } diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryRibbonClientConfiguration.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryRibbonClientConfiguration.java index 13d1df6..c3a4337 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryRibbonClientConfiguration.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryRibbonClientConfiguration.java @@ -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; } diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServer.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServer.java index e7f9422..fb05265 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServer.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServer.java @@ -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; } }; } diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java index 30e3662..3ffc4d5 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java @@ -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 { - 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 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 cloudFoundryServers() { + return cloudFoundryService + .getApplicationInstances(this.serviceId) + .map(tpl -> new CloudFoundryServer(tpl.getT1().getName(), tpl.getT1().getUrls().get(0), 80)) + .collectList() + .blockOptional() + .orElse(new ArrayList<>()); } } \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/AdhocTestSuite.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/AdhocTestSuite.java index 5d50223..b70a263 100644 --- a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/AdhocTestSuite.java +++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/AdhocTestSuite.java @@ -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 { diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java deleted file mode 100644 index 9cc144c..0000000 --- a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java +++ /dev/null @@ -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 Josh Long - */ -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); - } - -} diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientTest.java index 8453f32..dd24c75 100644 --- a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientTest.java +++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientTest.java @@ -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 Josh Long @@ -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 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 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 tuple2 = Tuples.of(applicationDetail, instanceDetail); + Mockito.when(svc.getApplicationInstances(this.hiServiceServiceId)).thenReturn(Flux.just(tuple2)); List 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 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); - } - } \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerListTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerListTest.java index c79347c..1c884d8 100644 --- a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerListTest.java +++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerListTest.java @@ -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 Josh Long @@ -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>() { - @Override - public List 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 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 initialListOfServers = this.cloudFoundryServerList - .getInitialListOfServers(); - List updatedListOfServers = this.cloudFoundryServerList - .getUpdatedListOfServers(); + List initialListOfServers = this.cloudFoundryServerList.getInitialListOfServers(); + List 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 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); - } } diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerTest.java deleted file mode 100644 index a92ac95..0000000 --- a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerTest.java +++ /dev/null @@ -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 Josh Long - */ -public class CloudFoundryServerTest { - - private CloudFoundryServer cloudFoundryServer; - private List 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)); - } -} diff --git a/spring-cloud-cloudfoundry-sample/src/main/java/org/springframework/cloud/cloudfoundry/sample/CloudFoundryApplication.java b/spring-cloud-cloudfoundry-sample/src/main/java/org/springframework/cloud/cloudfoundry/sample/CloudFoundryApplication.java index 109508f..b456253 100644 --- a/spring-cloud-cloudfoundry-sample/src/main/java/org/springframework/cloud/cloudfoundry/sample/CloudFoundryApplication.java +++ b/spring-cloud-cloudfoundry-sample/src/main/java/org/springframework/cloud/cloudfoundry/sample/CloudFoundryApplication.java @@ -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 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)); + }); } }