From 109b93385c7ac0cb75af6a12ecfbc0d94febe36b Mon Sep 17 00:00:00 2001 From: Stefan Pfeiffer Date: Fri, 18 Jan 2019 19:53:08 +0100 Subject: [PATCH] Respect spring.cloud.config.name value in ServiceMatcher (#143) To get notified about changes in properties/yml files that are not named after the service name but still get pulled from the config server, the value of spring.cloud.config.name is now honored in addition to the service id when matching destination service ids of incoming RemoteRefreshEvents. Fixes gh-132 --- .../cloud/bus/BusAutoConfiguration.java | 11 +- .../cloud/bus/ServiceMatcher.java | 30 +++- .../ServiceMatcherWithConfigNamesTests.java | 143 ++++++++++++++++++ 3 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 spring-cloud-bus/src/test/java/org/springframework/cloud/bus/ServiceMatcherWithConfigNamesTests.java diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java index ea4cd9d..2b3aaab 100644 --- a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java @@ -20,6 +20,7 @@ package org.springframework.cloud.bus; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.trace.http.HttpTraceRepository; @@ -54,6 +55,7 @@ import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; +import org.springframework.core.env.Environment; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.AntPathMatcher; @@ -73,6 +75,8 @@ public class BusAutoConfiguration implements ApplicationEventPublisherAware { public static final String BUS_PATH_MATCHER_NAME = "busPathMatcher"; + public static final String CLOUD_CONFIG_NAME_PROPERTY = "spring.cloud.config.name"; + private MessageChannel cloudBusOutboundChannel; private ApplicationEventPublisher applicationEventPublisher; @@ -182,8 +186,11 @@ public class BusAutoConfiguration implements ApplicationEventPublisherAware { @Bean public ServiceMatcher serviceMatcher(@BusPathMatcher PathMatcher pathMatcher, - BusProperties properties) { - ServiceMatcher serviceMatcher = new ServiceMatcher(pathMatcher, properties.getId()); + BusProperties properties, Environment environment) { + String[] configNames = environment.getProperty(CLOUD_CONFIG_NAME_PROPERTY, + String[].class, new String[] {}); + ServiceMatcher serviceMatcher = new ServiceMatcher(pathMatcher, + properties.getId(), configNames); return serviceMatcher; } diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java index 6dbe3dd..d399c9b 100644 --- a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java @@ -26,12 +26,27 @@ import org.springframework.util.PathMatcher; public class ServiceMatcher { private final PathMatcher matcher; private final String id; + private String[] configNames = new String[] {}; public ServiceMatcher(PathMatcher matcher, String id) { this.matcher = matcher; this.id = id; } + public ServiceMatcher(PathMatcher matcher, String id, String[] configNames) { + this(matcher, id); + + int colonIndex = id.indexOf(":"); + if (colonIndex >= 0) { + // if the id contains profiles and port, append them to the config names + String profilesAndPort = id.substring(colonIndex); + for (int i = 0; i < configNames.length; i++) { + configNames[i] = configNames[i] + profilesAndPort; + } + } + this.configNames = configNames; + } + public boolean isFromSelf(RemoteApplicationEvent event) { String originService = event.getOriginService(); String serviceId = getServiceId(); @@ -40,8 +55,19 @@ public class ServiceMatcher { public boolean isForSelf(RemoteApplicationEvent event) { String destinationService = event.getDestinationService(); - return (destinationService == null || destinationService.trim().isEmpty() - || this.matcher.match(destinationService, getServiceId())); + if (destinationService == null || destinationService.trim().isEmpty() + || this.matcher.match(destinationService, getServiceId())) { + return true; + } + + // Check all potential config names instead of service name + for (String configName : this.configNames) { + if (this.matcher.match(destinationService, configName)) { + return true; + } + } + + return false; } public String getServiceId() { diff --git a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/ServiceMatcherWithConfigNamesTests.java b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/ServiceMatcherWithConfigNamesTests.java new file mode 100644 index 0000000..de9412f --- /dev/null +++ b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/ServiceMatcherWithConfigNamesTests.java @@ -0,0 +1,143 @@ +/* + * Copyright 2013-2019 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.bus; + +import java.util.Collections; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent; +import org.springframework.util.AntPathMatcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * @author Stefan Pfeiffer + * + */ +public class ServiceMatcherWithConfigNamesTests { + + private static final Map EMPTY_MAP = Collections.emptyMap(); + + private ServiceMatcher matcher; + + @Before + public void init() { + initMatcher("otherid:two:8888", new String[] {"one", "three"}); + } + + private void initMatcher(String id, String[] configNames) { + BusProperties properties = new BusProperties(); + properties.setId(id); + DefaultBusPathMatcher pathMatcher = new DefaultBusPathMatcher(new AntPathMatcher(":")); + matcher = new ServiceMatcher(pathMatcher, properties.getId(), configNames); + } + + @Test + public void forSelfWithWildcard() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "one:two:*", EMPTY_MAP)), is(true)); + } + + @Test + public void forSelfWithWildcardAndOtherConfigName() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "three:two:*", EMPTY_MAP)), is(true)); + } + + @Test + public void forSelfWithGlobalWildcard() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "**", EMPTY_MAP)), is(true)); + } + + @Test + public void forSelfWithWildcardName() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "o*", EMPTY_MAP)), is(true)); + } + + @Test + public void forSelfWithWildcardNameAndProfile() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "o*:t*", EMPTY_MAP)), is(true)); + } + + @Test + public void forSelfWithWildcardString() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "o*", EMPTY_MAP)), is(true)); + } + + @Test + public void notForSelfWithWildCardNameAndMismatchingProfile() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "o*:f*", EMPTY_MAP)), is(false)); + } + + @Test + public void forSelfWithDoubleWildcard() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "one:**", EMPTY_MAP)), is(true)); + } + + @Test + public void forSelfWithNoWildcard() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "one", EMPTY_MAP)), is(true)); + } + + @Test + public void forSelfWithProfileNoWildcard() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "one:two", EMPTY_MAP)), is(true)); + } + + @Test + public void notForSelf() { + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "one:two:9999", EMPTY_MAP)), is(false)); + } + + @Test + public void forSelfWithMultipleProfiles() { + initMatcher("customerportal:dev,cloud:80", new String[] {"one", "three"}); + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "one:cloud:*", EMPTY_MAP)), is(true)); + } + + @Test + public void notForSelfWithMultipleProfiles() { + initMatcher("customerportal:dev,cloud:80", new String[] {"one", "three"}); + assertThat(matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "bar:cloud:*", EMPTY_MAP)), is(false)); + } + + @Test + public void notForSelfWithMultipleProfilesDifferentPort() { + initMatcher("customerportal:dev,cloud:80", new String[] {"one", "three"}); + assertThat( + matcher.isForSelf(new EnvironmentChangeRemoteApplicationEvent(this, + "foo:bar:spam", "customerportal:cloud:8008", EMPTY_MAP)), + is(false)); + } + +}