From 6e81c007e64317c8e8492b1ac137d4b5a691832f Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 27 Jun 2019 14:07:07 -0700 Subject: [PATCH] Add comparable methods to optionally 'find' as well as 'require' a CloudCache Service Instance in PCF. Resolves gh-33. --- .../geode/core/env/VcapPropertySource.java | 82 +++++-- .../core/env/VcapPropertySourceUnitTests.java | 208 +++++++++++++----- 2 files changed, 206 insertions(+), 84 deletions(-) diff --git a/spring-geode/src/main/java/org/springframework/geode/core/env/VcapPropertySource.java b/spring-geode/src/main/java/org/springframework/geode/core/env/VcapPropertySource.java index a342b8ea..7f8131a0 100644 --- a/spring-geode/src/main/java/org/springframework/geode/core/env/VcapPropertySource.java +++ b/spring-geode/src/main/java/org/springframework/geode/core/env/VcapPropertySource.java @@ -70,9 +70,9 @@ public class VcapPropertySource extends PropertySource CLOUD_CACHE_SERVICE_PREDICATE = propertyValue -> String.valueOf(propertyValue).toLowerCase().contains(CLOUD_CACHE_TAG_NAME); @@ -124,10 +124,12 @@ public class VcapPropertySource extends PropertySource newIllegalArgumentException( - "A valid EnumerablePropertySource named [%s] containing VCAP properties is required", + "An EnumerablePropertySource named [%s] containing VCAP properties is required", VCAP_PROPERTY_SOURCE_NAME)); } + private Predicate vcapServicePredicate; + /** * Constructs a new {@link PropertySource} from the existing, required {@link EnumerablePropertySource} instance * with the default name, {@literal boot.data.gemfire.vcap}, containing the {@literal VCAP} environment variable @@ -172,39 +174,58 @@ public class VcapPropertySource extends PropertySource findFirstCloudCacheService() { - String serviceName = findFirstCloudCacheServiceName(); + return findFirstCloudCacheServiceName() + .map(serviceName -> { - CloudCacheService service = CloudCacheService.with(serviceName); + CloudCacheService service = CloudCacheService.with(serviceName); - Optional.ofNullable(getProperty(String.format(VCAP_SERVICES_SERVICE_NAME_LOCATORS_PROPERTY, serviceName))) - .map(String::valueOf) - .ifPresent(service::withLocators); + Object locators = getProperty(String.format(VCAP_SERVICES_SERVICE_NAME_LOCATORS_PROPERTY, service)); - Optional.ofNullable(getProperty(String.format(VCAP_SERVICES_SERVICE_NAME_GFSH_URL_PROPERTY, service))) - .map(String::valueOf) - .map(urlString -> ObjectUtils.doOperationSafely(() -> new URL(urlString))) - .ifPresent(service::withGfshUrl); + Optional.ofNullable(locators) + .map(String::valueOf) + .filter(StringUtils::hasText) + .ifPresent(service::withLocators); - return service; + Object gfshUrl = getProperty(String.format(VCAP_SERVICES_SERVICE_NAME_GFSH_URL_PROPERTY, service)); + + Optional.ofNullable(gfshUrl) + .map(String::valueOf) + .filter(StringUtils::hasText) + .map(urlString -> ObjectUtils.doOperationSafely(() -> new URL(urlString))) + .ifPresent(service::withGfshUrl); + + return service; + }); } - public String findFirstCloudCacheServiceName() { + public CloudCacheService requireFirstCloudCacheService() { + + return findFirstCloudCacheService().orElseThrow(() -> + newIllegalStateException("Unable to resolve a CloudCache Service Instance")); + } + + public Optional findFirstCloudCacheServiceName() { Iterable vcapServicesProperties = findAllVcapServicesProperties(); - return findAllPropertiesByValueMatching(vcapServicesProperties, CLOUD_CACHE_AND_GEMFIRE_SERVICE_PREDICATE) - .stream() + Predicate vcapServicePredicate = resolveVcapServicePredicate(); + + return findAllPropertiesByValueMatching(vcapServicesProperties, vcapServicePredicate).stream() .filter(propertyName -> propertyName.endsWith(".tags")) .map(propertyName -> propertyName.substring(VCAP_SERVICES_PROPERTY.length())) .map(propertyName -> propertyName.substring(0, propertyName.indexOf("."))) .filter(StringUtils::hasText) - .sorted(String.CASE_INSENSITIVE_ORDER) - .findFirst() - .orElseThrow(() -> - newIllegalStateException("No service with tags [%1$s, %2$s] was found", - CLOUD_CACHE_TAG_NAME, GEMFIRE_TAG_NAME)); + .min(String.CASE_INSENSITIVE_ORDER); + } + + public String requireFirstCloudCacheServiceName() { + + String tags = String.format("%1$s, %2$s", CLOUD_CACHE_TAG_NAME, GEMFIRE_TAG_NAME); + + return findFirstCloudCacheServiceName() + .orElseThrow(() -> newIllegalStateException("No service with tags [%s] was found", tags)); } public Optional findFirstUserByRoleClusterOperator(Service service) { @@ -238,9 +259,15 @@ public class VcapPropertySource extends PropertySource resolveVcapServicePredicate() { + + return this.vcapServicePredicate != null + ? this.vcapServicePredicate + : CLOUD_CACHE_AND_GEMFIRE_SERVICE_PREDICATE; + } + @Nullable @Override - @SuppressWarnings("all") public Object getProperty(String name) { return getSource().getProperty(name); } @@ -250,4 +277,11 @@ public class VcapPropertySource extends PropertySource iterator() { return Collections.unmodifiableList(Arrays.asList(getSource().getPropertyNames())).iterator(); } + + public VcapPropertySource withVcapServicePredicate(Predicate vcapServicePredicate) { + + this.vcapServicePredicate = vcapServicePredicate; + + return this; + } } diff --git a/spring-geode/src/test/java/org/springframework/geode/core/env/VcapPropertySourceUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/core/env/VcapPropertySourceUnitTests.java index 71c73c8f..461feca0 100644 --- a/spring-geode/src/test/java/org/springframework/geode/core/env/VcapPropertySourceUnitTests.java +++ b/spring-geode/src/test/java/org/springframework/geode/core/env/VcapPropertySourceUnitTests.java @@ -13,7 +13,6 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ - package org.springframework.geode.core.env; import static org.assertj.core.api.Assertions.assertThat; @@ -30,10 +29,12 @@ import static org.mockito.Mockito.when; import java.net.URL; import java.util.Arrays; +import java.util.Optional; import java.util.Properties; import java.util.Set; import org.junit.Test; + import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; @@ -70,8 +71,8 @@ public class VcapPropertySourceUnitTests { when(mockEnvironment.getPropertySources()).thenReturn(propertySources); doReturn(mockVcapPropertySource).when(propertySources).get(eq("vcap")); - when(mockVcapPropertySource.getName()).thenReturn("vcap"); when(mockVcapPropertySource.containsProperty(anyString())).thenReturn(true); + when(mockVcapPropertySource.getName()).thenReturn("vcap"); VcapPropertySource propertySource = VcapPropertySource.from(mockEnvironment); @@ -131,6 +132,7 @@ public class VcapPropertySourceUnitTests { throw expected; } finally { + verify(mockEnvironment, times(1)).getPropertySources(); verify(propertySources, times(1)).get(eq("vcap")); } @@ -154,8 +156,8 @@ public class VcapPropertySourceUnitTests { } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessage( - "A valid EnumerablePropertySource named [vcap] containing VCAP properties is required", + assertThat(expected) + .hasMessage("An EnumerablePropertySource named [vcap] containing VCAP properties is required", mockEnvironment); assertThat(expected).hasNoCause(); @@ -163,6 +165,7 @@ public class VcapPropertySourceUnitTests { throw expected; } finally { + verify(mockEnvironment, times(1)).getPropertySources(); verify(propertySources, times(1)).get(eq("vcap")); verify(mockPropertySource, times(1)).getName(); @@ -183,9 +186,9 @@ public class VcapPropertySourceUnitTests { when(mockEnvironment.getPropertySources()).thenReturn(propertySources); doReturn(mockPropertySource).when(propertySources).get(eq("vcap")); - when(mockPropertySource.getName()).thenReturn("vcap"); when(mockPropertySource.containsProperty(eq("vcap.application.name"))).thenReturn(true); when(mockPropertySource.containsProperty(eq("vcap.application.uris"))).thenReturn(false); + when(mockPropertySource.getName()).thenReturn("vcap"); try { VcapPropertySource.from(mockEnvironment); @@ -193,7 +196,7 @@ public class VcapPropertySourceUnitTests { catch (IllegalArgumentException expected) { assertThat(expected).hasMessage( - "A valid EnumerablePropertySource named [vcap] containing VCAP properties is required", + "An EnumerablePropertySource named [vcap] containing VCAP properties is required", mockEnvironment); assertThat(expected).hasNoCause(); @@ -201,6 +204,7 @@ public class VcapPropertySourceUnitTests { throw expected; } finally { + verify(mockEnvironment, times(1)).getPropertySources(); verify(propertySources, times(1)).get(eq("vcap")); verify(mockPropertySource, times(1)).getName(); @@ -216,16 +220,15 @@ public class VcapPropertySourceUnitTests { Properties vcap = new Properties(); - vcap.setProperty("vcap.application.name", "testApp"); - vcap.setProperty("vcap.application.uris", "boot-app.apps.cloud.net"); - + vcap.setProperty("vcap.application.name", "TestApp"); + vcap.setProperty("vcap.application.uris", "test-app.boot-app.apps.cloud.net"); VcapPropertySource propertySource = VcapPropertySource.from(vcap); assertThat(propertySource).isNotNull(); assertThat(propertySource.getSource()).isInstanceOf(PropertiesPropertySource.class); - assertThat(propertySource.getProperty("vcap.application.name")).isEqualTo("testApp"); - assertThat(propertySource.getProperty("vcap.application.uris")).isEqualTo("boot-app.apps.cloud.net"); + assertThat(propertySource.getProperty("vcap.application.name")).isEqualTo("TestApp"); + assertThat(propertySource.getProperty("vcap.application.uris")).isEqualTo("test-app.boot-app.apps.cloud.net"); } @Test(expected = IllegalArgumentException.class) @@ -276,11 +279,10 @@ public class VcapPropertySourceUnitTests { "vcap.services.jblum-pcc.tags" }; - when(mockPropertySource.getName()).thenReturn("vcap"); - when(mockPropertySource.containsProperty(anyString())).thenAnswer(invocation -> Arrays.asList(propertyNames).contains(invocation.getArgument(0))); + when(mockPropertySource.getName()).thenReturn("vcap"); when(mockPropertySource.getPropertyNames()).thenReturn(propertyNames); VcapPropertySource propertySource = VcapPropertySource.from(mockPropertySource); @@ -322,11 +324,10 @@ public class VcapPropertySourceUnitTests { "vcap.services.jblum-pcc.tags" }; - when(mockPropertySource.getName()).thenReturn("vcap"); - when(mockPropertySource.containsProperty(anyString())).thenAnswer(invocation -> Arrays.asList(propertyNames).contains(invocation.getArgument(0))); + when(mockPropertySource.getName()).thenReturn("vcap"); when(mockPropertySource.getPropertyNames()).thenReturn(propertyNames); VcapPropertySource propertySource = VcapPropertySource.from(mockPropertySource); @@ -334,11 +335,11 @@ public class VcapPropertySourceUnitTests { assertThat(propertySource).isNotNull(); assertThat(propertySource.getSource()).isEqualTo(mockPropertySource); - Set vcapApplicationProperties = propertySource.findAllVcapServicesProperties(); + Set vcapServicesProperties = propertySource.findAllVcapServicesProperties(); - assertThat(vcapApplicationProperties).isNotNull(); - assertThat(vcapApplicationProperties).hasSize(5); - assertThat(vcapApplicationProperties) + assertThat(vcapServicesProperties).isNotNull(); + assertThat(vcapServicesProperties).hasSize(5); + assertThat(vcapServicesProperties) .containsExactlyInAnyOrder("vcap.services.jblum-pcc.credentials.locators", "vcap.services.jblum-pcc.credentials.users", "vcap.services.jblum-pcc.name", "vcap.services.jblum-pcc.plan", "vcap.services.jblum-pcc.tags"); @@ -352,7 +353,7 @@ public class VcapPropertySourceUnitTests { } @Test - public void findFirstCloudCacheServiceNameReturnsServiceName() { + public void findFirstCloudCacheServiceNameReturnsOptionalOfServiceName() { Properties vcap = new Properties(); @@ -362,65 +363,83 @@ public class VcapPropertySourceUnitTests { vcap.setProperty("vcap.services.test-pcc.tags", "pivotal,database,cloudcache,gemfire"); vcap.setProperty("vcap.application.space_name", "outerspace"); vcap.setProperty("vcap.services.jblum-pcc.name", "jblum-pcc"); - vcap.setProperty("vcap.services.jblum-pcc.plan", "small"); + vcap.setProperty("vcap.services.jblum-pcc.plan", "medium"); vcap.setProperty("vcap.services.jblum-pcc.tags", "cloudcache,database,gemfire,pivotal"); vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); + vcap.setProperty("vcap.services.mock-pcc.name", "mock-pcc"); + vcap.setProperty("vcap.services.mock-pcc.plan", "large"); + vcap.setProperty("vcap.services.mock-pcc.tags", "pivotal,cloudcache,database"); + + VcapPropertySource propertySource = VcapPropertySource.from(vcap); + + assertThat(propertySource).isNotNull(); + assertThat(propertySource.findFirstCloudCacheServiceName().orElse(null)).isEqualTo("jblum-pcc"); + } + + @Test + public void findFirstCloudCacheServiceNameReturnsEmptyOptional() { + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "boot-example"); + vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); vcap.setProperty("vcap.services.a-pcc.name", "a-pcc"); - vcap.setProperty("vcap.services.a-pcc.plan", "huge"); + vcap.setProperty("vcap.services.a-pcc.plan", "large"); vcap.setProperty("vcap.services.a-pcc.tags", "pivotal,cloudcache,database"); + vcap.setProperty("vcap.services.b-pcc.name", "b-pcc"); + vcap.setProperty("vcap.services.b-pcc.plan", "small"); + vcap.setProperty("vcap.services.b-pcc.tags", "pivotal,database,gemfire"); VcapPropertySource propertySource = VcapPropertySource.from(vcap); assertThat(propertySource).isNotNull(); - assertThat(propertySource.findFirstCloudCacheServiceName()).isEqualTo("jblum-pcc"); + assertThat(propertySource.findFirstCloudCacheServiceName().isPresent()).isFalse(); } - @Test(expected = IllegalStateException.class) - public void findFirstCloudCacheServiceNameWithInvalidTagsThrowsIllegalStateException() { + @Test + public void requireFirstCloudCacheServiceNameReturnsServiceName() { Properties vcap = new Properties(); vcap.setProperty("vcap.application.name", "boot-example"); - vcap.setProperty("vcap.services.test-pcc.name", "test-pcc"); - vcap.setProperty("vcap.services.test-pcc.plan", "small"); - vcap.setProperty("vcap.services.test-pcc.tags", "pivotal,gemfire,database"); + vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); + vcap.setProperty("vcap.services.a-pcc.name", "a-pcc"); + vcap.setProperty("vcap.services.a-pcc.plan", "large"); + vcap.setProperty("vcap.services.a-pcc.tags", "pivotal,cloudcache,database"); + vcap.setProperty("vcap.services.b-pcc.name", "b-pcc"); + vcap.setProperty("vcap.services.b-pcc.plan", "small"); + vcap.setProperty("vcap.services.b-pcc.tags", "pivotal,database,gemfire"); + vcap.setProperty("vcap.services.c-pcc.name", "c-pcc"); + vcap.setProperty("vcap.services.c-pcc.plan", "medium"); + vcap.setProperty("vcap.services.c-pcc.tags", "pivotal,cloudcache,database,gemfire"); + + VcapPropertySource propertySource = VcapPropertySource.from(vcap); + + assertThat(propertySource).isNotNull(); + assertThat(propertySource.findFirstCloudCacheServiceName().orElse(null)).isEqualTo("c-pcc"); + } + + @Test(expected = IllegalStateException.class) + public void requireFirstCloudCacheServiceNameWithInvalidTagsThrowsIllegalStateException() { + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "boot-example"); + vcap.setProperty("vcap.services.c-pcc.name", "c-pcc"); + vcap.setProperty("vcap.services.c-pcc.plan", "small"); + vcap.setProperty("vcap.services.c-pcc.tags", "pivotal,database,gemfire"); vcap.setProperty("vcap.application.space_name", "outerspace"); vcap.setProperty("vcap.services.a-pcc.tags", "pivotal,cloudcache,database"); vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); + vcap.setProperty("vcap.services.b-pcc.name", "b-pcc"); + vcap.setProperty("vcap.services.b-pcc.plan", "large"); VcapPropertySource propertySource = VcapPropertySource.from(vcap); assertThat(propertySource).isNotNull(); try { - propertySource.findFirstCloudCacheServiceName(); - } - catch (IllegalStateException expected) { - - assertThat(expected).hasMessage("No service with tags [cloudcache, gemfire] was found"); - assertThat(expected).hasNoCause(); - - throw expected; - } - } - - @Test(expected = IllegalStateException.class) - public void findFirstCloudCacheServiceNameWithNoTagsThrowsIllegalStateException() { - - Properties vcap = new Properties(); - - vcap.setProperty("vcap.application.name", "boot-example"); - vcap.setProperty("vcap.services.test-pcc.name", "test-pcc"); - vcap.setProperty("vcap.services.test-pcc.plan", "small"); - vcap.setProperty("vcap.application.space_name", "outerspace"); - vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); - - VcapPropertySource propertySource = VcapPropertySource.from(vcap); - - assertThat(propertySource).isNotNull(); - - try { - propertySource.findFirstCloudCacheServiceName(); + propertySource.requireFirstCloudCacheServiceName(); } catch (IllegalStateException expected) { @@ -432,7 +451,48 @@ public class VcapPropertySourceUnitTests { } @Test - public void findFirstCloudCacheServiceReturnsCloudCacheService() throws Exception { + public void findFirstCloudCacheServiceReturnsOptionalOfCloudCacheService() { + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "boot-example"); + vcap.setProperty("vcap.services.test-pcc.name", "test-pcc"); + vcap.setProperty("vcap.application.space_name", "outerspace"); + vcap.setProperty("vcap.services.test-pcc.tags", "pivotal,cloudcache,database,gemfire,junk"); + vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); + + VcapPropertySource propertySource = VcapPropertySource.from(vcap); + + assertThat(propertySource).isNotNull(); + + Optional cloudCacheService = propertySource.findFirstCloudCacheService(); + + assertThat(cloudCacheService).isNotNull(); + assertThat(cloudCacheService.isPresent()).isTrue(); + assertThat(cloudCacheService.map(CloudCacheService::getName).orElse(null)).isEqualTo("test-pcc"); + assertThat(cloudCacheService.flatMap(CloudCacheService::getLocators).isPresent()).isFalse(); + assertThat(cloudCacheService.flatMap(CloudCacheService::getGfshUrl).isPresent()).isFalse(); + } + + @Test + public void findFirstCloudCacheServiceReturnsEmptyOptional() { + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "boot-example"); + vcap.setProperty("vcap.services.test-postresql.name", "TestPostreSQLDatabase"); + vcap.setProperty("vcap.application.space_name", "outerspace"); + vcap.setProperty("vcap.services.test-postresql.tags", "pivotal, database, postresql"); + vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); + + VcapPropertySource propertySource = VcapPropertySource.from(vcap); + + assertThat(propertySource).isNotNull(); + assertThat(propertySource.findFirstCloudCacheService().isPresent()).isFalse(); + } + + @Test + public void requireFirstCloudCacheServiceReturnsCloudCacheService() throws Exception { URL gfshUrl = new URL("https://skullbox:7070/v1/gemfire"); @@ -451,9 +511,10 @@ public class VcapPropertySourceUnitTests { assertThat(propertySource).isNotNull(); - CloudCacheService cloudCacheService = propertySource.findFirstCloudCacheService(); + CloudCacheService cloudCacheService = propertySource.requireFirstCloudCacheService(); assertThat(cloudCacheService).isNotNull(); + assertThat(cloudCacheService.getName()).isEqualTo("test-pcc"); assertThat(cloudCacheService.getGfshUrl().orElse(null)).isEqualTo(gfshUrl); assertThat(cloudCacheService.getLocatorList()).containsExactly( CloudCacheService.Locator.newLocator("sandbox", 1234), @@ -462,6 +523,33 @@ public class VcapPropertySourceUnitTests { ); } + @Test(expected = IllegalStateException.class) + public void requireFirstCloudCacheServiceWhenNotFoundThrowsIllegalStateException() { + + Properties vcap = new Properties(); + + vcap.setProperty("vcap.application.name", "boot-example"); + vcap.setProperty("vcap.services.test-postresql.name", "TestPostreSQLDatabase"); + vcap.setProperty("vcap.application.space_name", "outerspace"); + vcap.setProperty("vcap.services.test-postresql.tags", "pivotal, database, postresql"); + vcap.setProperty("vcap.application.uris", "boot-example.boot-apps.apps.cloud.net"); + + VcapPropertySource propertySource = VcapPropertySource.from(vcap); + + assertThat(propertySource).isNotNull(); + + try { + propertySource.requireFirstCloudCacheService(); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("Unable to resolve a CloudCache Service Instance"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + @Test public void findFirstUserByRoleClusterOperatorReturnsUser() { @@ -505,13 +593,13 @@ public class VcapPropertySourceUnitTests { assertThat(root).isNotNull(); assertThat(root.getName()).isEqualTo("root"); assertThat(root.getPassword().orElse(null)).isEqualTo("p@55w0rd"); - assertThat(root.getRole().orElse(null).isClusterOperator()).isTrue(); + assertThat(root.getRole().map(User.Role::isClusterOperator).orElse(false)).isTrue(); User majorTom = propertySource.findFirstUserByRoleClusterOperator(Service.with("jblum-pcc")).orElse(null); assertThat(majorTom).isNotNull(); assertThat(majorTom.getName()).isEqualTo("majorTom"); assertThat(majorTom.getPassword().orElse(null)).isEqualTo("s3cUr3"); - assertThat(majorTom.getRole().orElse(null).isClusterOperator()).isTrue(); + assertThat(majorTom.getRole().map(User.Role::isClusterOperator).orElse(false)).isTrue(); } }