Added support for ReactiveDiscoveryClient; fixes gh-1392

This commit is contained in:
Marcin Grzejszczak
2020-07-24 15:27:41 +02:00
parent db6f51038a
commit 3ebd51cdaa
5 changed files with 262 additions and 24 deletions

View File

@@ -16,7 +16,6 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
@@ -41,8 +40,7 @@ import org.springframework.util.StringUtils;
*/
class StubRunnerDiscoveryClient implements DiscoveryClient {
private static final Log log = LogFactory
.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(StubRunnerDiscoveryClient.class);
private final DiscoveryClient delegate;
@@ -51,7 +49,7 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
private final StubMapperProperties stubMapperProperties;
StubRunnerDiscoveryClient(DiscoveryClient delegate, StubFinder stubFinder,
StubMapperProperties stubMapperProperties, String springAppName) {
StubMapperProperties stubMapperProperties) {
this.delegate = delegate instanceof StubRunnerDiscoveryClient
? noOpDiscoveryClient() : delegate;
if (log.isDebugEnabled()) {
@@ -63,7 +61,7 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
}
StubRunnerDiscoveryClient(StubFinder stubFinder,
StubMapperProperties stubMapperProperties, String springAppName) {
StubMapperProperties stubMapperProperties) {
this.delegate = noOpDiscoveryClient();
if (log.isDebugEnabled()) {
log.debug("Will delegate calls to discovery service [" + this.delegate
@@ -97,13 +95,13 @@ class StubRunnerDiscoveryClient implements DiscoveryClient {
String serviceToFind = StringUtils.hasText(ivyNotation) ? ivyNotation : serviceId;
URL stubUrl = this.stubFinder.findStubUrl(serviceToFind);
log.info("Resolved from ivy [" + ivyNotation + "] service to find ["
+ serviceToFind + "]. " + "Found stub is available under URL [" + stubUrl
+ serviceToFind + "]. Found stub is available under URL [" + stubUrl
+ "]");
if (stubUrl == null) {
return getInstancesFromDelegate(serviceId);
}
return Collections.<ServiceInstance>singletonList(new StubRunnerServiceInstance(
serviceId, stubUrl.getHost(), stubUrl.getPort(), toUri(stubUrl)));
return Collections.singletonList(new StubRunnerServiceInstance(serviceId,
stubUrl.getHost(), stubUrl.getPort(), toUri(stubUrl)));
}
private List<ServiceInstance> getInstancesFromDelegate(String serviceId) {

View File

@@ -0,0 +1,125 @@
/*
* 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.contract.stubrunner.StubFinder;
/**
* Custom version of {@link DiscoveryClient} that tries to find an instance in one of the
* started WireMock servers.
*
* @author Marcin Grzejszczak
* @since 2.2.4
*/
class StubRunnerReactiveDiscoveryClient implements ReactiveDiscoveryClient {
private static final Log log = LogFactory
.getLog(StubRunnerReactiveDiscoveryClient.class);
private final ReactiveDiscoveryClient delegate;
private final StubFinder stubFinder;
private final StubMapperProperties stubMapperProperties;
StubRunnerReactiveDiscoveryClient(ReactiveDiscoveryClient delegate,
StubFinder stubFinder, StubMapperProperties stubMapperProperties) {
this.delegate = delegate instanceof StubRunnerDiscoveryClient
? noOpDiscoveryClient() : delegate;
if (log.isDebugEnabled()) {
log.debug("Will delegate calls to discovery service [" + this.delegate
+ "] if a stub is not found");
}
this.stubFinder = stubFinder;
this.stubMapperProperties = stubMapperProperties;
}
StubRunnerReactiveDiscoveryClient(StubFinder stubFinder,
StubMapperProperties stubMapperProperties) {
this.delegate = noOpDiscoveryClient();
if (log.isDebugEnabled()) {
log.debug("Will delegate calls to discovery service [" + this.delegate
+ "] if a stub is not found");
}
this.stubFinder = stubFinder;
this.stubMapperProperties = stubMapperProperties;
}
private StubRunnerNoOpReactiveDiscoveryClient noOpDiscoveryClient() {
return new StubRunnerNoOpReactiveDiscoveryClient();
}
@Override
public String description() {
try {
return this.delegate.description();
}
catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Failed to fetch description from delegate", e);
}
}
return "";
}
@Override
public Flux<ServiceInstance> getInstances(String serviceId) {
return Flux.just(client()).flatMapIterable(c -> c.getInstances(serviceId));
}
private StubRunnerDiscoveryClient client() {
return new StubRunnerDiscoveryClient(this.stubFinder, this.stubMapperProperties);
}
@Override
public Flux<String> getServices() {
return Flux.just(client())
.flatMapIterable(StubRunnerDiscoveryClient::getServices);
}
@Override
public int getOrder() {
return this.delegate.getOrder();
}
}
class StubRunnerNoOpReactiveDiscoveryClient implements ReactiveDiscoveryClient {
@Override
public String description() {
return "Spring Cloud Stub Runner No-op ReactiveDiscoveryClient";
}
@Override
public Flux<ServiceInstance> getInstances(String serviceId) {
return Flux.empty();
}
@Override
public Flux<String> getServices() {
return Flux.empty();
}
}

View File

@@ -18,13 +18,13 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
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.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.contract.stubrunner.StubFinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -55,10 +55,18 @@ public class StubRunnerSpringCloudAutoConfiguration {
@ConditionalOnProperty(value = "stubrunner.cloud.delegate.enabled",
havingValue = "false", matchIfMissing = true)
public DiscoveryClient noOpStubRunnerDiscoveryClient(StubFinder stubFinder,
StubMapperProperties stubMapperProperties,
@Value("${spring.application.name:unknown}") String springAppName) {
return new StubRunnerDiscoveryClient(stubFinder, stubMapperProperties,
springAppName);
StubMapperProperties stubMapperProperties) {
return new StubRunnerDiscoveryClient(stubFinder, stubMapperProperties);
}
@Bean
@ConditionalOnMissingBean(ReactiveDiscoveryClient.class)
@ConditionalOnStubbedDiscoveryEnabled
@ConditionalOnProperty(value = "stubrunner.cloud.delegate.enabled",
havingValue = "false", matchIfMissing = true)
public ReactiveDiscoveryClient noOpStubRunnerReactiveDiscoveryClient(
StubFinder stubFinder, StubMapperProperties stubMapperProperties) {
return new StubRunnerReactiveDiscoveryClient(stubFinder, stubMapperProperties);
}
}
@@ -97,10 +105,9 @@ class StubRunnerDiscoveryClientWrapper implements BeanPostProcessor {
}
if (isCloudDelegateEnabled()) {
return new StubRunnerDiscoveryClient((DiscoveryClient) bean, stubFinder(),
stubMapperProperties(), springAppName());
stubMapperProperties());
}
return new StubRunnerDiscoveryClient(stubFinder(), stubMapperProperties(),
springAppName());
return new StubRunnerDiscoveryClient(stubFinder(), stubMapperProperties());
}
return bean;
}
@@ -120,14 +127,6 @@ class StubRunnerDiscoveryClientWrapper implements BeanPostProcessor {
return this.stubMapperProperties;
}
String springAppName() {
if (this.springAppName == null) {
this.springAppName = this.beanFactory.getBean(Environment.class)
.getProperty("spring.application.name", "unknown");
}
return this.springAppName;
}
boolean isStubbedDiscoveryEnabled() {
if (this.stubbedDiscoveryEnabled == null) {
this.stubbedDiscoveryEnabled = Boolean

View File

@@ -0,0 +1,99 @@
/*
* 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.SpringBootContextLoader
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.test.context.ContextConfiguration
import org.springframework.web.client.RestTemplate
/**
* @author Marcin Grzejszczak
*/
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@ActiveProfiles("cloudtest-no-ribbon")
// 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 StubRunnerSpringCloudReactiveNoRibbonAutoConfigurationSpec 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()
}
}
}

View File

@@ -0,0 +1,17 @@
stubrunner:
cloud.stubbed.discovery.enabled: true
spring.cloud:
zookeeper:
enabled: false
discovery.enabled: false
consul:
enabled: false
discovery.enabled: false
service-registry.enabled: false
eureka.client.enabled: false
ribbon.eureka.enabled: false
spring:
cloud:
loadbalancer:
ribbon:
enabled: false # Use Reactive Load Balancer