diff --git a/docker/spring-cloud-contract-stub-runner-docker/pom.xml b/docker/spring-cloud-contract-stub-runner-docker/pom.xml index 7901a9a844..cc2dd708fa 100644 --- a/docker/spring-cloud-contract-stub-runner-docker/pom.xml +++ b/docker/spring-cloud-contract-stub-runner-docker/pom.xml @@ -40,6 +40,13 @@ pom runtime + + org.springframework.cloud + spring-cloud-netflix-dependencies + ${spring-cloud-netflix.version} + pom + runtime + org.springframework.cloud spring-cloud-zookeeper-dependencies diff --git a/pom.xml b/pom.xml index 23c702d75e..6438ba9e64 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT 5.0.4 @@ -274,6 +275,13 @@ pom import + + org.springframework.cloud + spring-cloud-netflix-dependencies + ${spring-cloud-netflix.version} + pom + import + org.springframework.cloud spring-cloud-stream-dependencies diff --git a/spring-cloud-contract-stub-runner/pom.xml b/spring-cloud-contract-stub-runner/pom.xml index 03a7a37e84..50aa6d7cca 100644 --- a/spring-cloud-contract-stub-runner/pom.xml +++ b/spring-cloud-contract-stub-runner/pom.xml @@ -211,6 +211,11 @@ curator-test test + + org.springframework.cloud + spring-cloud-netflix-eureka-server + true + org.springframework.cloud spring-cloud-starter-consul-discovery diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/ConditionalOnEurekaEnabled.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/ConditionalOnEurekaEnabled.java new file mode 100644 index 0000000000..2ad62d3eb5 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/ConditionalOnEurekaEnabled.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.contract.stubrunner.spring.cloud.eureka; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +/** + * Conditional that checks if Eureka is enabled. + * + * @author Marcin Grzejszczak + * + * @since 1.0.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@ConditionalOnProperty(value = "eureka.client.enabled", havingValue = "true", matchIfMissing = true) +@interface ConditionalOnEurekaEnabled { + +} diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java new file mode 100644 index 0000000000..ad13b2e6c0 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java @@ -0,0 +1,163 @@ +/* + * Copyright 2013-2020 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 + * + * https://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.contract.stubrunner.spring.cloud.eureka; + +import java.lang.invoke.MethodHandles; +import java.net.InetAddress; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; +import com.netflix.discovery.EurekaClient; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.contract.stubrunner.StubConfiguration; +import org.springframework.cloud.contract.stubrunner.StubRunning; +import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties; +import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar; +import org.springframework.cloud.netflix.eureka.CloudEurekaClient; +import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean; +import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean; +import org.springframework.cloud.netflix.eureka.InstanceInfoFactory; +import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration; +import org.springframework.context.ApplicationContext; +import org.springframework.util.StringUtils; + +/** + * Registers all stubs in Eureka Service Discovery. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class EurekaStubsRegistrar implements StubsRegistrar { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final StubRunning stubRunning; + + private final StubMapperProperties stubMapperProperties; + + private final InetUtils inetUtils; + + private final EurekaInstanceConfigBean eurekaInstanceConfigBean; + + private final EurekaClientConfigBean eurekaClientConfigBean; + + private final List registrations = new LinkedList<>(); + + private final ServiceRegistry serviceRegistry; + + private final ApplicationContext context; + + public EurekaStubsRegistrar(StubRunning stubRunning, ServiceRegistry serviceRegistry, + StubMapperProperties stubMapperProperties, InetUtils inetUtils, + EurekaInstanceConfigBean eurekaInstanceConfigBean, EurekaClientConfigBean eurekaClientConfigBean, + ApplicationContext context) { + this.stubRunning = stubRunning; + this.stubMapperProperties = stubMapperProperties; + this.serviceRegistry = serviceRegistry; + this.inetUtils = inetUtils; + this.eurekaInstanceConfigBean = eurekaInstanceConfigBean; + this.eurekaClientConfigBean = eurekaClientConfigBean; + this.context = context; + } + + @Override + public void registerStubs() { + Map activeStubs = this.stubRunning.runStubs().validNamesAndPorts(); + for (Map.Entry entry : activeStubs.entrySet()) { + EurekaInstanceConfigBean instance = registration(entry); + log.info("Will register stub in Eureka " + "[" + instance.getAppname() + ", " + instance.getHostname() + + ", " + instance.getNonSecurePort() + ", " + instance.getInstanceId() + "]"); + InstanceInfo instanceInfo = new InstanceInfoFactory().create(instance); + ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(instance, instanceInfo); + AbstractDiscoveryClientOptionalArgs args = args(); + EurekaClient client = new CloudEurekaClient(applicationInfoManager, this.eurekaClientConfigBean, args, + this.context); + EurekaRegistration registration = EurekaRegistration.builder(instance) + .with(this.eurekaClientConfigBean, this.context).with(client).build(); + this.registrations.add(registration); + try { + this.serviceRegistry.register(registration); + log.info("Successfully registered stub " + "[" + entry.getKey().toColonSeparatedDependencyNotation() + + "] in Service Discovery"); + } + catch (Exception e) { + log.warn("Exception occurred while trying to register a stub [" + + entry.getKey().toColonSeparatedDependencyNotation() + "] in Service Discovery", e); + } + } + } + + private AbstractDiscoveryClientOptionalArgs args() { + try { + return this.context.getBean(AbstractDiscoveryClientOptionalArgs.class); + } + catch (BeansException e) { + return null; + } + } + + private EurekaInstanceConfigBean registration(Map.Entry entry) { + EurekaInstanceConfigBean config = new EurekaInstanceConfigBean(this.inetUtils); + String appName = name(entry.getKey()); + config.setInstanceEnabledOnit(true); + InetAddress address = this.inetUtils.findFirstNonLoopbackAddress(); + config.setIpAddress(address.getHostAddress()); + config.setHostname(StringUtils.hasText(hostName(entry)) ? hostName(entry) : address.getHostName()); + config.setAppname(appName); + config.setVirtualHostName(appName); + config.setSecureVirtualHostName(appName); + int port = port(entry); + config.setNonSecurePort(port); + config.setInstanceId(address.getHostAddress() + ":" + entry.getKey().getArtifactId() + ":" + port); + config.setLeaseRenewalIntervalInSeconds(1); + return config; + } + + protected String hostName(Map.Entry entry) { + return this.eurekaInstanceConfigBean.getHostname(); + } + + protected int port(Map.Entry entry) { + return entry.getValue(); + } + + private String name(StubConfiguration stubConfiguration) { + String resolvedName = this.stubMapperProperties + .fromIvyNotationToId(stubConfiguration.toColonSeparatedDependencyNotation()); + if (StringUtils.hasText(resolvedName)) { + return resolvedName; + } + return stubConfiguration.getArtifactId(); + } + + @Override + public void close() throws Exception { + for (EurekaRegistration registration : this.registrations) { + this.serviceRegistry.deregister(registration); + } + } + +} diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfiguration.java new file mode 100644 index 0000000000..2b40a56d20 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfiguration.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013-2020 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 + * + * https://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.contract.stubrunner.spring.cloud.eureka; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.contract.stubrunner.StubConfiguration; +import org.springframework.cloud.contract.stubrunner.StubRunning; +import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfiguration; +import org.springframework.cloud.contract.stubrunner.spring.cloud.ConditionalOnStubbedDiscoveryDisabled; +import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties; +import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar; +import org.springframework.cloud.netflix.eureka.CloudEurekaClient; +import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration; +import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean; +import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean; +import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; + +/** + * Autoconfiguration for registering stubs in a Eureka Service discovery. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +@Configuration(proxyBeanMethods = false) +@AutoConfigureAfter({ StubRunnerConfiguration.class, EurekaClientAutoConfiguration.class }) +@ConditionalOnClass(CloudEurekaClient.class) +@ConditionalOnStubbedDiscoveryDisabled +@ConditionalOnEurekaEnabled +@ConditionalOnProperty(value = "stubrunner.cloud.eureka.enabled", matchIfMissing = true) +public class StubRunnerSpringCloudEurekaAutoConfiguration { + + @Profile("!cloud") + @Configuration(proxyBeanMethods = false) + protected static class NonCloudConfig { + + @Bean(initMethod = "registerStubs") + public StubsRegistrar stubsRegistrar(StubRunning stubRunning, + ServiceRegistry serviceRegistry, ApplicationContext context, + StubMapperProperties stubMapperProperties, InetUtils inetUtils, + EurekaInstanceConfigBean eurekaInstanceConfigBean, EurekaClientConfigBean eurekaClientConfigBean) { + return new EurekaStubsRegistrar(stubRunning, serviceRegistry, stubMapperProperties, inetUtils, + eurekaInstanceConfigBean, eurekaClientConfigBean, context); + } + + } + + @Profile("cloud") + @Configuration(proxyBeanMethods = false) + protected static class CloudConfig { + + private static final int DEFAULT_PORT = 80; + + private static final Log log = LogFactory.getLog(CloudConfig.class); + + @Autowired + Environment environment; + + @Bean(initMethod = "registerStubs") + public StubsRegistrar stubsRegistrar(StubRunning stubRunning, + ServiceRegistry serviceRegistry, ApplicationContext context, + StubMapperProperties stubMapperProperties, InetUtils inetUtils, + EurekaInstanceConfigBean eurekaInstanceConfigBean, EurekaClientConfigBean eurekaClientConfigBean) { + return new EurekaStubsRegistrar(stubRunning, serviceRegistry, stubMapperProperties, inetUtils, + eurekaInstanceConfigBean, eurekaClientConfigBean, context) { + @Override + protected String hostName(Map.Entry entry) { + String hostname = CloudConfig.this.environment.getProperty("application.hostname") + "-" + + entry.getValue() + "." + CloudConfig.this.environment.getProperty("application.domain"); + log.info("Registering stub [" + entry.getKey().getArtifactId() + "] with hostname [" + hostname + + "]"); + return hostname; + } + + @Override + protected int port(Map.Entry entry) { + return DEFAULT_PORT; + } + }; + } + + } + +} diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java new file mode 100644 index 0000000000..5158dda531 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2020 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 + * + * https://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.contract.stubrunner.serverexamples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer; +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + +/** + * @author Marcin Grzejszczak + */ +// tag::stubrunnereureka[] +@SpringBootApplication +@EnableStubRunnerServer +@EnableEurekaClient +@AutoConfigureStubRunner +public class StubRunnerBootEurekaExample { + + public static void main(String[] args) { + SpringApplication.run(StubRunnerBootEurekaExample.class, args); + } + +} +// end::stubrunnereureka[] + +/* + * + * // tag::stubrunnereureka_args[] + * -Dstubrunner.repositoryRoot=https://repo.spring.io/snapshot (1) + * -Dstubrunner.cloud.stubbed.discovery.enabled=false (2) + * -Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org. + * springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework. + * cloud.contract.verifier.stubs:bootService (3) + * -Dstubrunner.idsToServiceIds.fraudDetectionServer= + * someNameThatShouldMapFraudDetectionServer (4) + * + * (1) - we tell Stub Runner where all the stubs reside (2) - we don't want the default + * behaviour where the discovery service is stubbed. That's why the stub registration will + * be picked (3) - we provide a list of stubs to download (4) - we provide a list of + * artifactId to serviceId mapping // end::stubrunnereureka_args[] + * + * + * -Dstubrunner.cloud.eureka.enabled=true + * -Dstubrunner.repositoryRoot=classpath:m2repo/repository/ + * -Dstubrunner.camel.enabled=false -Dspring.cloud.zookeeper.enabled=false + * -Dspring.cloud.zookeeper.discovery.enabled=false -Ddebug=true + * + */ diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy new file mode 100644 index 0000000000..aafcf1e6ff --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy @@ -0,0 +1,96 @@ +/* + * Copyright 2013-2020 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 + * + * https://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.contract.stubrunner.spring.cloud + +import spock.lang.Specification + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cloud.client.loadbalancer.LoadBalanced +import org.springframework.cloud.consul.ConsulAutoConfiguration +import org.springframework.cloud.contract.stubrunner.StubFinder +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner +import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties +import org.springframework.cloud.contract.stubrunner.spring.cloud.loadbalancer.StubRunnerLoadBalancerClientFactory +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory +import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration +import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.test.context.ActiveProfiles +import org.springframework.web.client.RestTemplate + +/** + * @author Marcin Grzejszczak + */ +@SpringBootTest(classes = Config) +@ActiveProfiles("cloudtest") +// tag::autoconfigure[] +@AutoConfigureStubRunner( + ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", + "org.springframework.cloud.contract.verifier.stubs:bootService"], + stubsMode = StubRunnerProperties.StubsMode.REMOTE, + repositoryRoot = "classpath:m2repo/repository/") +// end::autoconfigure[] +class StubRunnerSpringCloudAutoConfigurationSpec extends Specification { + + @Autowired + StubFinder stubFinder + @Autowired + @LoadBalanced + RestTemplate restTemplate + @Autowired + LoadBalancerClientFactory loadBalancerClientFactory; + + void setupSpec() { + System.clearProperty("stubrunner.repository.root") + System.clearProperty("stubrunner.classifier") + } + + void cleanupSpec() { + setupSpec() + } + + def setup() { + assert loadBalancerClientFactory instanceof StubRunnerLoadBalancerClientFactory + } + + // tag::test[] + def 'should make service discovery work'() { + expect: 'WireMocks are running' + "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' + "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' + and: 'Stubs can be reached via load service discovery' + restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' + restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' + } + // end::test[] + + @Configuration + @EnableAutoConfiguration(exclude = [EurekaClientAutoConfiguration, + ConsulAutoConfiguration, ZookeeperAutoConfiguration]) + static class Config { + + @Bean + @LoadBalanced + RestTemplate restTemplate() { + return new RestTemplate() + } + } +} diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudReactiveAutoConfigurationSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudReactiveAutoConfigurationSpec.groovy new file mode 100644 index 0000000000..1cd17eacab --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudReactiveAutoConfigurationSpec.groovy @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2020 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 + * + * https://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.contract.stubrunner.spring.cloud + +import org.junit.AfterClass +import org.junit.BeforeClass +import spock.lang.Specification + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient +import org.springframework.cloud.client.loadbalancer.LoadBalanced +import org.springframework.cloud.consul.ConsulAutoConfiguration +import org.springframework.cloud.contract.stubrunner.StubFinder +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner +import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties +import org.springframework.cloud.contract.stubrunner.spring.cloud.loadbalancer.StubRunnerLoadBalancerClientFactory +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory +import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration +import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.test.context.ActiveProfiles +import org.springframework.web.client.RestTemplate + +/** + * @author Marcin Grzejszczak + */ +@SpringBootTest(classes = Config) +@ActiveProfiles("cloudtest") +@AutoConfigureStubRunner( + ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", + "org.springframework.cloud.contract.verifier.stubs:bootService"], + stubsMode = StubRunnerProperties.StubsMode.REMOTE, + repositoryRoot = "classpath:m2repo/repository/") +class StubRunnerSpringCloudReactiveAutoConfigurationSpec extends Specification { + @Autowired + StubFinder stubFinder + @Autowired + ReactiveDiscoveryClient reactiveDiscoveryClient; + @Autowired + LoadBalancerClientFactory loadBalancerClientFactory; + RestTemplate restTemplate = new RestTemplate() + + @BeforeClass + @AfterClass + static void setupProps() { + System.clearProperty("stubrunner.repository.root") + System.clearProperty("stubrunner.classifier") + } + + def setup() { + assert loadBalancerClientFactory instanceof StubRunnerLoadBalancerClientFactory + } + + // tag::test[] + def 'should make service discovery work'() { + expect: 'WireMocks are running' + "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' + "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' + and: 'Stubs can be reached via load service discovery' + ServiceInstance loanIssuance = reactiveDiscoveryClient.getInstances('loanIssuance').blockFirst() + restTemplate.getForObject(loanIssuance.uri.toString() + '/name', String) == 'loanIssuance' + ServiceInstance fraudDetection = reactiveDiscoveryClient.getInstances('someNameThatShouldMapFraudDetectionServer').blockFirst() + restTemplate.getForObject(fraudDetection.uri.toString() + '/name', String) == 'fraudDetectionServer' + } + // end::test[] + + @Configuration + @EnableAutoConfiguration(exclude = [EurekaClientAutoConfiguration, + ConsulAutoConfiguration, ZookeeperAutoConfiguration]) + static class Config { + + @Bean + @LoadBalanced + RestTemplate restTemplate() { + return new RestTemplate() + } + } +} diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/pom.xml b/tests/spring-cloud-contract-stub-runner-boot-eureka/pom.xml new file mode 100644 index 0000000000..5e4dc31ec6 --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-contract-tests + 4.0.0-SNAPSHOT + .. + + spring-cloud-contract-stub-runner-boot-eureka + jar + Spring Cloud Contract Stub Runner Boot Eureka + Spring Cloud Contract Stub Runner Boot Eureka + + 1.19.1 + + + + org.springframework.cloud + spring-cloud-contract-stub-runner + + + com.sun.jersey.contribs + jersey-apache-client4 + ${jersey-apache-client4.version} + true + + + org.springframework.cloud + spring-cloud-starter-contract-stub-runner-jetty + test + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + true + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-server + true + + + junit + junit + test + + + org.codehaus.groovy + groovy + + + org.spockframework + spock-core + test + + + org.spockframework + spock-spring + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web + test + + + org.springframework.boot + spring-boot-starter-webflux + test + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfigurationSpec.groovy b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfigurationSpec.groovy new file mode 100644 index 0000000000..207c8c44b0 --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfigurationSpec.groovy @@ -0,0 +1,173 @@ +/* + * Copyright 2013-2020 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 + * + * https://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.contract.stubrunner.spring.cloud.eureka + +import javax.servlet.Filter +import javax.servlet.FilterChain +import javax.servlet.FilterConfig +import javax.servlet.ServletException +import javax.servlet.ServletRequest +import javax.servlet.ServletResponse + +import spock.lang.AutoCleanup +import spock.lang.Shared +import spock.lang.Specification +import spock.util.concurrent.PollingConditions + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootContextLoader +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cloud.client.loadbalancer.LoadBalanced +import org.springframework.cloud.contract.stubrunner.StubFinder +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner +import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties +import org.springframework.cloud.netflix.eureka.EnableEurekaClient +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod +import org.springframework.http.client.ClientHttpResponse +import org.springframework.test.context.ContextConfiguration +import org.springframework.web.client.DefaultResponseErrorHandler +import org.springframework.web.client.RequestCallback +import org.springframework.web.client.ResponseExtractor +import org.springframework.web.client.RestClientException +import org.springframework.web.client.RestTemplate + +/** + * @author Marcin Grzejszczak + */ +//TODO: Speed up this test somehow (move it out of Spring Cloud Contract core to samples) +@SpringBootTest(classes = Config, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = ["stubrunner.cloud.eureka.enabled=true", + "stubrunner.cloud.stubbed.discovery.enabled=false", + "stubrunner.cloud.ribbon.enabled=false", + "eureka.client.enabled=true", + "eureka.instance.leaseRenewalIntervalInSeconds=1", + "ribbon.ServerListRefreshInterval=100"]) +@AutoConfigureStubRunner(ids = +["org.springframework.cloud.contract.verifier.stubs:loanIssuance", + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", + "org.springframework.cloud.contract.verifier.stubs:bootService"] , +repositoryRoot = "classpath:m2repo/repository/" , +stubsMode = StubRunnerProperties.StubsMode.REMOTE ) +class StubRunnerSpringCloudEurekaAutoConfigurationSpec extends Specification { + + @Autowired + StubFinder stubFinder + @Autowired + @LoadBalanced + RestTemplate restTemplate + @Shared + @AutoCleanup + ConfigurableApplicationContext eurekaServer + + void setupSpec() { + System.clearProperty("stubrunner.stubs.repository.root") + System.clearProperty("stubrunner.stubs.classifier") + eurekaServer = SpringApplication.run(EurekaServer, + "--stubrunner.cloud.eureka.enabled=true", + "--stubrunner.cloud.stubbed.discovery.enabled=false", + "--eureka.client.enabled=true", + "--server.port=8761", + "--spring.profiles.active=eureka") + } + + void cleanupSpec() { + System.clearProperty("stubrunner.stubs.repository.root") + System.clearProperty("stubrunner.stubs.classifier") + } + + PollingConditions conditions = new PollingConditions(timeout: 240, delay: 1) + + def 'should make service discovery work'() { + expect: 'WireMocks are running' + "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' + "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' + and: 'Stubs can be reached via load service discovery' + conditions.eventually { + assert restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' + } + restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' + } + + @Configuration + @EnableAutoConfiguration + @EnableEurekaClient + static class Config { + + @Bean + @LoadBalanced + RestTemplate restTemplate() { + def template = new RestTemplate() { + + @Override + protected T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor responseExtractor) throws RestClientException { + try { + return super.doExecute(url, method, requestCallback, responseExtractor) + } + catch (Exception e) { + throw new AssertionError(e) + } + } + } + template.errorHandler = new DefaultResponseErrorHandler() { + @Override + void handleError(ClientHttpResponse response) throws IOException { + try { + super.handleError(response) + } + catch (Exception e) { + throw new AssertionError(e) + } + } + } + return template + } + } + + @Configuration + @EnableAutoConfiguration + @EnableEurekaServer + static class EurekaServer { + + @Bean + Filter httpTraceFilter() { + return new Filter() { + @Override + void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + filterChain.doFilter(servletRequest, servletResponse) + } + + @Override + void destroy() { + + } + } + } + + + } +} diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/application.yml b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/application.yml new file mode 100644 index 0000000000..2b91381a2c --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/application.yml @@ -0,0 +1,5 @@ +stubrunner: + idsToServiceIds: + fraudDetectionServer: someNameThatShouldMapFraudDetectionServer +server: + port: 0 diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/logback.xml b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/logback.xml new file mode 100644 index 0000000000..96d75b755b --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/logback.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/0.0.1-SNAPSHOT/bootService-0.0.1-SNAPSHOT-stubs.jar b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/0.0.1-SNAPSHOT/bootService-0.0.1-SNAPSHOT-stubs.jar new file mode 100644 index 0000000000..c57c836db4 Binary files /dev/null and b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/0.0.1-SNAPSHOT/bootService-0.0.1-SNAPSHOT-stubs.jar differ diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/0.0.1-SNAPSHOT/bootService-0.0.1-SNAPSHOT.pom b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/0.0.1-SNAPSHOT/bootService-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..dd16575a19 --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/0.0.1-SNAPSHOT/bootService-0.0.1-SNAPSHOT.pom @@ -0,0 +1,27 @@ + + + + + 4.0.0 + org.springframework.cloud.contract.verifier.stubs + bootService + 0.0.1-SNAPSHOT + pom + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/maven-metadata.xml b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/maven-metadata.xml new file mode 100644 index 0000000000..8461d4b2a6 --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/bootService/maven-metadata.xml @@ -0,0 +1,28 @@ + + + + + org.springframework.cloud.contract.verifier.stubs + bootService + 0.0.1-SNAPSHOT + + + 0.0.1-SNAPSHOT + + 20160409062112 + + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/0.0.1-SNAPSHOT/fraudDetectionServer-0.0.1-SNAPSHOT-stubs.jar b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/0.0.1-SNAPSHOT/fraudDetectionServer-0.0.1-SNAPSHOT-stubs.jar new file mode 100644 index 0000000000..fdb70e383f Binary files /dev/null and b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/0.0.1-SNAPSHOT/fraudDetectionServer-0.0.1-SNAPSHOT-stubs.jar differ diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/0.0.1-SNAPSHOT/fraudDetectionServer-0.0.1-SNAPSHOT.pom b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/0.0.1-SNAPSHOT/fraudDetectionServer-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..33e8194127 --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/0.0.1-SNAPSHOT/fraudDetectionServer-0.0.1-SNAPSHOT.pom @@ -0,0 +1,27 @@ + + + + + 4.0.0 + org.springframework.cloud.contract.verifier.stubs + fraudDetectionServer + 0.0.1-SNAPSHOT + pom + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/maven-metadata.xml b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/maven-metadata.xml new file mode 100644 index 0000000000..75dc4041ba --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/fraudDetectionServer/maven-metadata.xml @@ -0,0 +1,28 @@ + + + + + org.springframework.cloud.contract.verifier.stubs + fraudDetectionServer + 0.0.1-SNAPSHOT + + + 0.0.1-SNAPSHOT + + 20160409062112 + + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/0.0.1-SNAPSHOT/loanIssuance-0.0.1-SNAPSHOT-stubs.jar b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/0.0.1-SNAPSHOT/loanIssuance-0.0.1-SNAPSHOT-stubs.jar new file mode 100644 index 0000000000..e322590636 Binary files /dev/null and b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/0.0.1-SNAPSHOT/loanIssuance-0.0.1-SNAPSHOT-stubs.jar differ diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/0.0.1-SNAPSHOT/loanIssuance-0.0.1-SNAPSHOT.pom b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/0.0.1-SNAPSHOT/loanIssuance-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..ae3efaf070 --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/0.0.1-SNAPSHOT/loanIssuance-0.0.1-SNAPSHOT.pom @@ -0,0 +1,27 @@ + + + + + 4.0.0 + org.springframework.cloud.contract.verifier.stubs + loanIssuance + 0.0.1-SNAPSHOT + pom + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/maven-metadata.xml b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/maven-metadata.xml new file mode 100644 index 0000000000..4533bc03e8 --- /dev/null +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/resources/m2repo/repository/org/springframework/cloud/contract/verifier/stubs/loanIssuance/maven-metadata.xml @@ -0,0 +1,28 @@ + + + + + org.springframework.cloud.contract.verifier.stubs + loanIssuance + 0.0.1-SNAPSHOT + + + 0.0.1-SNAPSHOT + + 20160409062111 + +