Add @EnableClusterAware annotation configuration support allowing developers to seamlessly switch between local and client/server development environments.

Resolves gh-52.
This commit is contained in:
John Blum
2019-08-30 16:47:59 -07:00
parent 9bb6f6b0ea
commit c1d7424de1
8 changed files with 1064 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
/*
* Copyright 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
*
* 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.geode.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import javax.annotation.Resource;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.server.CacheServer;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.config.admin.GemfireAdminOperations;
import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate;
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnablePdx;
import org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport;
import org.springframework.test.context.junit4.SpringRunner;
import example.app.crm.model.Customer;
import example.app.crm.service.CustomerService;
/**
* Integration Tests asserting the functionality and behavior of {@link EnableClusterAware}
* and {@link ClusterAvailableConfiguration} when the Apache Geode (or Pivotal GemFire) cluster of servers is available.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.boot.test.context.SpringBootTest
* @see org.springframework.cache.annotation.Cacheable
* @see org.springframework.context.annotation.AnnotationConfigApplicationContext
* @see org.springframework.context.annotation.Bean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see org.springframework.data.gemfire.config.annotation.CacheServerApplication
* @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication
* @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions
* @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions
* @see org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport
* @see org.springframework.geode.config.annotation.ClusterAvailableConfiguration
* @see org.springframework.geode.config.annotation.EnableClusterAware
* @see org.springframework.test.context.junit4.SpringRunner
* @since 1.2.0
*/
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = ClusterAvailableConfigurationIntegrationTests.GeodeClientApplication.class,
properties = { "spring.data.gemfire.management.use-http=false" },
webEnvironment = SpringBootTest.WebEnvironment.NONE
)
@SuppressWarnings("unused")
public class ClusterAvailableConfigurationIntegrationTests extends ForkingClientServerIntegrationTestsSupport {
private static final String LOG_LEVEL = "error";
@BeforeClass
public static void runGemFireServer() throws IOException {
startGemFireServer(GeodeServerApplication.class);
}
@Autowired
private ClientCache clientCache;
@Resource(name = "Customers")
private Region<Long, Customer> customers;
@Resource(name = "CustomersByName")
private Region<String, Customer> customersByName;
@Resource(name = "Example")
private Region<Object, Object> example;
@Before
public void assertRegionConfiguration() {
assertRegion(this.example, "Example", DataPolicy.EMPTY, "DEFAULT");
assertRegion(this.customers, "Customers", DataPolicy.EMPTY, "DEFAULT");
assertRegion(this.customersByName, "CustomersByName", DataPolicy.EMPTY, "DEFAULT");
}
@Before
public void assertRegionConfigurationOnServers() {
GemfireAdminOperations adminOperations = new RestHttpGemfireAdminTemplate.Builder()
.with(this.clientCache)
.on("localhost")
.listenOn(Integer.getInteger(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, CacheServer.DEFAULT_PORT))
.build();
assertThat(adminOperations).isNotNull();
assertThat(adminOperations.getAvailableServerRegions())
.containsExactlyInAnyOrder("Customers", "CustomersByName", "Example");
}
private void assertRegion(Region<?, ?> region, String name, DataPolicy dataPolicy, String poolName) {
assertThat(region).isNotNull();
assertThat(region.getName()).isEqualTo(name);
assertThat(region.getAttributes()).isNotNull();
assertThat(region.getAttributes().getDataPolicy()).isEqualTo(dataPolicy);
assertThat(region.getAttributes().getPoolName()).isEqualTo(poolName);
}
@Test
public void customersRegionDataAccessOperationsWork() {
Customer jonDoe = Customer.newCustomer(1L, "Jon Doe");
assertThat(this.customers.put(jonDoe.getId(), jonDoe)).isNull();
assertThat(this.customers.get(jonDoe.getId())).isEqualTo(jonDoe);
}
@Test
public void customersByNameRegionDataAccessOperationsWork() {
Customer jonDoe = Customer.newCustomer(2L, "Jane Doe");
assertThat(this.customersByName.put(jonDoe.getName(), jonDoe)).isNull();
assertThat(this.customersByName.get(jonDoe.getName())).isEqualTo(jonDoe);
}
@Test
public void exampleRegionDataAccessOperationsWork() {
assertThat(this.example.put(1, "TEST")).isNull();
assertThat(this.example.get(1)).isEqualTo("TEST");
}
@CacheServerApplication(name = "ClusterAvailableConfigurationIntegrationTestsServer", logLevel = LOG_LEVEL)
static class GeodeServerApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(GeodeServerApplication.class);
applicationContext.registerShutdownHook();
}
}
@EnableClusterAware
@EnableCachingDefinedRegions
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
@EnablePdx
@ClientCacheApplication(name = "ClusterAvailableConfigurationIntegrationTests", logLevel = LOG_LEVEL)
static class GeodeClientApplication {
@Bean("Example")
ClientRegionFactoryBean<Object, Object> exampleRegion(GemFireCache cache) {
ClientRegionFactoryBean<Object, Object> exampleRegion = new ClientRegionFactoryBean<>();
exampleRegion.setCache(cache);
exampleRegion.setShortcut(ClientRegionShortcut.PROXY);
return exampleRegion;
}
@Bean
CustomerService customerService() {
return new CustomerService();
}
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright 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
*
* 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.geode.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
import org.springframework.data.gemfire.support.ConnectionEndpointList;
import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport;
/**
* Unit Tests for {@link EnableClusterAware} and {@link ClusterAwareConfiguration}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.mockito.Spy
* @see org.springframework.core.env.ConfigurableEnvironment
* @see org.springframework.data.gemfire.support.ConnectionEndpoint
* @see org.springframework.data.gemfire.support.ConnectionEndpointList
* @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport
* @see org.springframework.geode.config.annotation.ClusterAwareConfiguration
* @see org.springframework.geode.config.annotation.EnableClusterAware
* @since 1.2.0
*/
public class ClusterAwareConfigurationUnitTests extends IntegrationTestsSupport {
private ClusterAwareConfiguration.ClusterAwareCondition condition =
spy(new ClusterAwareConfiguration.ClusterAwareCondition());
@Before @After
public void setupAndTearDown() {
System.clearProperty(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY);
}
@Test
public void getDefaultConnectionEndpointsIncludesDefaultLocatorAndDefaultServer() {
assertThat(this.condition.getDefaultConnectionEndpoints().stream()
.map(ConnectionEndpoint::toString)
.collect(Collectors.toList()))
.containsExactly("localhost[40404]", "localhost[10334]");
}
@Test
public void getConfiguredConnectionEndpointsIsCorrect() {
ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class);
MutablePropertySources propertySources = new MutablePropertySources();
Properties locatorProperties = new Properties();
Properties serverProperties = new Properties();
locatorProperties.setProperty("spring.data.gemfire.pool.locators", "boombox[1234], cardboardbox[5678], mailbox[9012]");
locatorProperties.setProperty("spring.data.gemfire.pool.car.locators", "skullbox[11235]");
locatorProperties.setProperty("spring.data.gemfire.pool.other-property", "junk");
serverProperties.setProperty("spring.data.gemfire.pool.servers", "mars[41414]");
serverProperties.setProperty("spring.data.gemfire.pool.swimming.servers", "jupiter[42424], saturn[43434]");
serverProperties.setProperty("spring.data.gemfire.pool.other-property", "junk");
propertySources.addLast(new PropertiesPropertySource("LocatorPoolProperties", locatorProperties));
propertySources.addFirst(new PropertiesPropertySource("ServerPoolProperties", serverProperties));
when(mockEnvironment.getPropertySources()).thenReturn(propertySources);
when(mockEnvironment.getProperty(anyString())).thenAnswer(invocation -> {
String propertyName = invocation.getArgument(0);
return locatorProperties.getProperty(propertyName, serverProperties.getProperty(propertyName));
});
List<ConnectionEndpoint> connectionEndpoints = this.condition.getConfiguredConnectionEndpoints(mockEnvironment);
List<String> connectionEndpointStrings = connectionEndpoints.stream()
.map(ConnectionEndpoint::toString)
.collect(Collectors.toList());
assertThat(connectionEndpoints).isNotNull();
assertThat(connectionEndpoints).hasSize(7);
assertThat(connectionEndpointStrings)
.containsExactlyInAnyOrder("mars[41414]", "jupiter[42424]", "saturn[43434]", "boombox[1234]",
"cardboardbox[5678]", "mailbox[9012]", "skullbox[11235]");
verify(mockEnvironment, times(1)).getPropertySources();
verify(mockEnvironment, times(4)).getProperty(anyString());
}
@Test
public void countConnectionsIsCorrect() throws Exception {
ConnectionEndpointList list = new ConnectionEndpointList(
new ConnectionEndpoint("boombox", 1234),
new ConnectionEndpoint("cardboardbox", 5678),
new ConnectionEndpoint("mailbox", 9012),
new ConnectionEndpoint("skullbox", 10334)
);
Logger mockLogger = mock(Logger.class);
doReturn(mockLogger).when(this.condition).getLogger();
doAnswer(invocation -> {
ConnectionEndpoint connectionEndpoint = invocation.getArgument(0);
if (Arrays.asList("boombox", "skullbox").contains(connectionEndpoint.getHost())) {
return mock(Socket.class);
}
else {
throw new IOException("TEST");
}
}).when(this.condition).connect(any(ConnectionEndpoint.class));
assertThat(this.condition.countConnections(list)).isEqualTo(2);
}
@Test
public void configureTopologySetsLocalWhenConnectionCountIsLessThanOneAndEnvironmentDoesNotContainTargetProperty() {
assertThat(System.getProperties())
.doesNotContainKey(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY);
ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class);
ConnectionEndpointList connectionEndpoints =
new ConnectionEndpointList(new ConnectionEndpoint("localhost", 1234));
this.condition.configureTopology(mockEnvironment, connectionEndpoints,0);
assertThat(System.getProperty(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY))
.isEqualTo("LOCAL");
verify(mockEnvironment, times(1))
.containsProperty(eq(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY));
}
@Test
public void configureTopologySetsClientServerWhenConnectionCountIsGreaterThanEqualToOne() {
assertThat(System.getProperties())
.doesNotContainKey(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY);
ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class);
this.condition.configureTopology(mockEnvironment, null,1);
assertThat(System.getProperties())
.doesNotContainKey(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY);
verify(mockEnvironment, never())
.containsProperty(eq(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY));
}
@Test
public void configureTopologySetsClientServerWhenEnvironmentContainsTargetProperty() {
assertThat(System.getProperties())
.doesNotContainKey(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY);
ConfigurableEnvironment mockEnvironment = mock(ConfigurableEnvironment.class);
when(mockEnvironment.containsProperty(eq(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY)))
.thenReturn(true);
this.condition.configureTopology(mockEnvironment, null,0);
assertThat(System.getProperties())
.doesNotContainKey(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY);
verify(mockEnvironment, times(1))
.containsProperty(eq(ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY));
}
@Test
public void isMatchReturnsTrue() {
assertThat(this.condition.isMatch(null, 1)).isTrue();
}
@Test
public void isNotMatchReturnsFalse() {
ConnectionEndpointList connectionEndpoints =
ConnectionEndpointList.from(new ConnectionEndpoint("localhost", 1234));
assertThat(this.condition.isMatch(connectionEndpoints, 0)).isFalse();
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 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
*
* 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.geode.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import javax.annotation.Resource;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport;
import org.springframework.test.context.junit4.SpringRunner;
import example.app.crm.model.Customer;
import example.app.crm.service.CustomerService;
/**
* Integration Tests asserting the functionality and behavior of {@link EnableClusterAware}
* and {@link ClusterNotAvailableConfiguration} when the Apache Geode (or Pivotal GemFire) cluster of servers
* is not available.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.boot.test.context.SpringBootTest
* @see org.springframework.cache.annotation.Cacheable
* @see org.springframework.context.annotation.Bean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication
* @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions
* @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions
* @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport
* @see org.springframework.geode.config.annotation.ClusterNotAvailableConfiguration
* @see org.springframework.geode.config.annotation.EnableClusterAware
* @see org.springframework.test.context.junit4.SpringRunner
* @since 1.2.0
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@SuppressWarnings("unused")
public class ClusterNotAvailableConfigurationIntegrationTests extends IntegrationTestsSupport {
private static final String LOG_LEVEL = "error";
@Resource(name = "Customers")
private Region<Long, Customer> customers;
@Resource(name = "CustomersByName")
private Region<String, Customer> customersByName;
@Resource(name = "Example")
private Region<Object, Object> example;
@Before
public void setup() {
assertRegion(this.example, "Example", DataPolicy.NORMAL, null);
assertRegion(this.customers, "Customers", DataPolicy.NORMAL, null);
assertRegion(this.customersByName, "CustomersByName", DataPolicy.NORMAL, null);
}
private void assertRegion(Region<?, ?> region, String name, DataPolicy dataPolicy, String poolName) {
assertThat(region).isNotNull();
assertThat(region.getName()).isEqualTo(name);
assertThat(region.getAttributes()).isNotNull();
assertThat(region.getAttributes().getDataPolicy()).isEqualTo(dataPolicy);
assertThat(region.getAttributes().getPoolName()).isEqualTo(poolName);
}
@Test
public void customersRegionDataAccessOperationsWork() {
Customer jonDoe = Customer.newCustomer(1L, "Jon Doe");
assertThat(this.customers.put(jonDoe.getId(), jonDoe)).isNull();
assertThat(this.customers.get(jonDoe.getId())).isEqualTo(jonDoe);
}
@Test
public void customersByNameRegionDataAccessOperationsWork() {
Customer jonDoe = Customer.newCustomer(2L, "Jane Doe");
assertThat(this.customersByName.put(jonDoe.getName(), jonDoe)).isNull();
assertThat(this.customersByName.get(jonDoe.getName())).isEqualTo(jonDoe);
}
@Test
public void exampleRegionDataAccessOperationsWork() {
assertThat(this.example.put(1, "TEST")).isNull();
assertThat(this.example.get(1)).isEqualTo("TEST");
}
@EnableClusterAware
@EnableCachingDefinedRegions
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
@ClientCacheApplication(name = "ClusterAvailableConfigurationIntegrationTests", logLevel = LOG_LEVEL)
static class GeodeClientApplication {
@Bean("Example")
ClientRegionFactoryBean<Object, Object> exampleRegion(GemFireCache cache) {
ClientRegionFactoryBean<Object, Object> exampleRegion = new ClientRegionFactoryBean<>();
exampleRegion.setCache(cache);
exampleRegion.setShortcut(ClientRegionShortcut.PROXY);
return exampleRegion;
}
@Bean
CustomerService customerService() {
return new CustomerService();
}
}
}