You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by bo...@apache.org on 2018/08/06 20:53:37 UTC

[geode] branch develop updated: GEODE-5502: Removed duplicate / member-specific receivers from cluster configuration

This is an automated email from the ASF dual-hosted git repository.

boglesby pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 2ae2b59  GEODE-5502: Removed duplicate / member-specific receivers from cluster configuration
2ae2b59 is described below

commit 2ae2b591378a2cae8f5dee8f01bc07de8cce6d6e
Author: Barry Oglesby <bo...@users.noreply.github.com>
AuthorDate: Mon Aug 6 13:53:32 2018 -0700

    GEODE-5502: Removed duplicate / member-specific receivers from cluster configuration
---
 .../InternalConfigurationPersistenceService.java   |  62 ++++-
 ...nternalConfigurationPersistenceServiceTest.java | 128 ++++++++++-
 .../test/dunit/standalone/VersionManager.java      |   1 +
 .../cache/wan/WANRollingUpgradeDUnitTest.java      |  43 +++-
 ...ipleReceiversDefinedInClusterConfiguration.java | 254 +++++++++++++++++++++
 5 files changed, 480 insertions(+), 8 deletions(-)

diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceService.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceService.java
index a6d7380..fae9b74 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceService.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceService.java
@@ -128,7 +128,7 @@ public class InternalConfigurationPersistenceService implements ConfigurationPer
   /**
    * Name of the region which is used to store the configuration information
    */
-  private static final String CONFIG_REGION_NAME = "_ConfigurationRegion";
+  public static final String CONFIG_REGION_NAME = "_ConfigurationRegion";
 
   private final String configDirPath;
   private final String configDiskDirPath;
@@ -517,6 +517,7 @@ public class InternalConfigurationPersistenceService implements ConfigurationPer
     this.status.set(SharedConfigurationStatus.STARTED);
     Region<String, Configuration> configRegion = this.getConfigurationRegion();
     lockSharedConfiguration();
+    removeInvalidXmlConfigurations(configRegion);
     try {
       if (loadSharedConfigFromDir) {
         logger.info("Reading cluster configuration from '{}' directory",
@@ -543,6 +544,65 @@ public class InternalConfigurationPersistenceService implements ConfigurationPer
     this.status.set(SharedConfigurationStatus.RUNNING);
   }
 
+  void removeInvalidXmlConfigurations(Region<String, Configuration> configRegion)
+      throws IOException, SAXException, ParserConfigurationException, TransformerException {
+    for (Map.Entry<String, Configuration> entry : configRegion.entrySet()) {
+      String group = entry.getKey();
+      Configuration configuration = entry.getValue();
+      String configurationXml = configuration.getCacheXmlContent();
+      if (configurationXml != null && !configurationXml.isEmpty()) {
+        Document document = XmlUtils.createDocumentFromXml(configurationXml);
+        boolean removedInvalidReceivers = removeInvalidGatewayReceivers(document);
+        boolean removedDuplicateReceivers = removeDuplicateGatewayReceivers(document);
+        if (removedInvalidReceivers || removedDuplicateReceivers) {
+          configuration.setCacheXmlContent(XmlUtils.prettyXml(document));
+          configRegion.put(group, configuration);
+        }
+      }
+    }
+  }
+
+  boolean removeInvalidGatewayReceivers(Document document) throws TransformerException {
+    boolean modified = false;
+    NodeList receiverNodes = document.getElementsByTagName("gateway-receiver");
+    for (int i = receiverNodes.getLength() - 1; i >= 0; i--) {
+      Element receiverElement = (Element) receiverNodes.item(i);
+
+      // Check hostname-for-senders
+      String hostNameForSenders = receiverElement.getAttribute("hostname-for-senders");
+      if (StringUtils.isNotBlank(hostNameForSenders)) {
+        receiverElement.getParentNode().removeChild(receiverElement);
+        logger.info("Removed invalid cluster configuration gateway-receiver element="
+            + XmlUtils.prettyXml(receiverElement));
+        modified = true;
+      }
+
+      // Check bind-address
+      String bindAddress = receiverElement.getAttribute("bind-address");
+      if (StringUtils.isNotBlank(bindAddress) && !bindAddress.equals("0.0.0.0")) {
+        receiverElement.getParentNode().removeChild(receiverElement);
+        logger.info("Removed invalid cluster configuration gateway-receiver element="
+            + XmlUtils.prettyXml(receiverElement));
+        modified = true;
+      }
+    }
+    return modified;
+  }
+
+  boolean removeDuplicateGatewayReceivers(Document document) throws TransformerException {
+    boolean modified = false;
+    NodeList receiverNodes = document.getElementsByTagName("gateway-receiver");
+    while (receiverNodes.getLength() > 1) {
+      Element receiverElement = (Element) receiverNodes.item(0);
+      receiverElement.getParentNode().removeChild(receiverElement);
+      logger.info("Removed duplicate cluster configuration gateway-receiver element="
+          + XmlUtils.prettyXml(receiverElement));
+      modified = true;
+      receiverNodes = document.getElementsByTagName("gateway-receiver");
+    }
+    return modified;
+  }
+
   private void persistSecuritySettings(final Region<String, Configuration> configRegion) {
     Properties securityProps = this.cache.getDistributedSystem().getSecurityProperties();
 
diff --git a/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceServiceTest.java b/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceServiceTest.java
index e658fc9..6dbf018 100644
--- a/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceServiceTest.java
+++ b/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalConfigurationPersistenceServiceTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.geode.distributed.internal;
 
+import static junitparams.JUnitParamsRunner.$;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -27,10 +28,19 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import java.util.AbstractMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+import org.junit.runner.RunWith;
+import org.w3c.dom.Document;
 
 import org.apache.geode.cache.Region;
 import org.apache.geode.cache.configuration.CacheConfig;
@@ -41,8 +51,9 @@ import org.apache.geode.internal.config.JAXBServiceTest.ElementOne;
 import org.apache.geode.internal.config.JAXBServiceTest.ElementTwo;
 import org.apache.geode.internal.lang.SystemPropertyHelper;
 import org.apache.geode.management.internal.configuration.domain.Configuration;
+import org.apache.geode.management.internal.configuration.utils.XmlUtils;
 
-
+@RunWith(JUnitParamsRunner.class)
 public class InternalConfigurationPersistenceServiceTest {
   private InternalConfigurationPersistenceService service, service2;
   private Configuration configuration;
@@ -193,4 +204,119 @@ public class InternalConfigurationPersistenceServiceTest {
     assertThat(packages).hasSize(2);
     assertThat(packages).contains("org.apache.geode", "io.pivotal");
   }
+
+  @Test
+  public void updateGatewayReceiverConfig() {
+    service.updateCacheConfig("cluster", cacheConfig -> {
+      CacheConfig.GatewayReceiver receiver = new CacheConfig.GatewayReceiver();
+      cacheConfig.setGatewayReceiver(receiver);
+      return cacheConfig;
+    });
+
+    System.out.println(configuration.getCacheXmlContent());
+    assertThat(configuration.getCacheXmlContent()).contains("<gateway-receiver/>");
+  }
+
+  @Test
+  public void removeDuplicateGatewayReceiversWithDefaultProperties() throws Exception {
+    Document document =
+        XmlUtils.createDocumentFromXml(getDuplicateReceiversWithDefaultPropertiesXml());
+    System.out.println("Initial document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(2);
+    service.removeDuplicateGatewayReceivers(document);
+    System.out.println("Processed document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(1);
+  }
+
+  @Test
+  public void removeInvalidGatewayReceiversWithDifferentHostNameForSenders() throws Exception {
+    Document document =
+        XmlUtils.createDocumentFromXml(getDuplicateReceiversWithDifferentHostNameForSendersXml());
+    System.out.println("Initial document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(2);
+    service.removeInvalidGatewayReceivers(document);
+    System.out.println("Processed document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(0);
+  }
+
+  @Test
+  public void removeInvalidGatewayReceiversWithDifferentBindAddresses() throws Exception {
+    Document document =
+        XmlUtils.createDocumentFromXml(getDuplicateReceiversWithDifferentBindAddressesXml());
+    System.out.println("Initial document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(2);
+    service.removeInvalidGatewayReceivers(document);
+    System.out.println("Processed document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(0);
+  }
+
+  @Test
+  public void keepValidGatewayReceiversWithDefaultBindAddress() throws Exception {
+    Document document =
+        XmlUtils.createDocumentFromXml(getSingleReceiverWithDefaultBindAddressXml());
+    System.out.println("Initial document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(1);
+    service.removeInvalidGatewayReceivers(document);
+    System.out.println("Processed document:\n" + XmlUtils.prettyXml(document));
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength()).isEqualTo(1);
+  }
+
+  @Test
+  @Parameters(method = "getXmlAndExpectedElements")
+  public void removeInvalidXmlConfiguration(String xml, int expectedInitialElements,
+      int expectFinalElements) throws Exception {
+    Region<String, Configuration> configurationRegion = mock(Region.class);
+    configuration.setCacheXmlContent(xml);
+    System.out.println("Initial xml content:\n" + configuration.getCacheXmlContent());
+    Document document = XmlUtils.createDocumentFromXml(configuration.getCacheXmlContent());
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength())
+        .isEqualTo(expectedInitialElements);
+    Set<Map.Entry<String, Configuration>> configurationEntries = new HashSet<>();
+    configurationEntries.add(new AbstractMap.SimpleEntry<>("cluster", configuration));
+    doReturn(configurationEntries).when(configurationRegion).entrySet();
+    service.removeInvalidXmlConfigurations(configurationRegion);
+    System.out.println("Processed xml content:\n" + configuration.getCacheXmlContent());
+    document = XmlUtils.createDocumentFromXml(configuration.getCacheXmlContent());
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength())
+        .isEqualTo(expectFinalElements);
+  }
+
+  private String getDuplicateReceiversWithDefaultPropertiesXml() {
+    return "<cache>\n<gateway-receiver/>\n<gateway-receiver/>\n</cache>";
+  }
+
+  private String getDuplicateReceiversWithDifferentHostNameForSendersXml() {
+    return "<cache>\n<gateway-receiver hostname-for-senders=\"123.12.12.12\"/>\n<gateway-receiver hostname-for-senders=\"123.12.12.11\"/>\n</cache>";
+  }
+
+  private String getDuplicateReceiversWithDifferentBindAddressesXml() {
+    return "<cache>\n<gateway-receiver bind-address=\"123.12.12.12\"/>\n<gateway-receiver bind-address=\"123.12.12.11\"/>\n</cache>";
+  }
+
+  private String getSingleReceiverWithDefaultBindAddressXml() {
+    return "<cache>\n<gateway-receiver bind-address=\"0.0.0.0\"/>\n</cache>";
+  }
+
+  private String getDuplicateReceiversWithDefaultBindAddressesXml() {
+    return "<cache>\n<gateway-receiver bind-address=\"0.0.0.0\"/>\n<gateway-receiver bind-address=\"0.0.0.0\"/>\n</cache>";
+  }
+
+  private String getValidReceiversXml() {
+    return "<cache>\n<gateway-receiver/>\n</cache>";
+  }
+
+  private String getNoReceiversXml() {
+    return "<cache>\n</cache>";
+  }
+
+  protected Object[] getXmlAndExpectedElements() {
+    return $(
+        new Object[] {getDuplicateReceiversWithDefaultPropertiesXml(), 2, 1},
+        new Object[] {getDuplicateReceiversWithDifferentHostNameForSendersXml(), 2, 0},
+        new Object[] {getDuplicateReceiversWithDifferentBindAddressesXml(), 2, 0},
+        new Object[] {getSingleReceiverWithDefaultBindAddressXml(), 1, 1},
+        new Object[] {getDuplicateReceiversWithDefaultBindAddressesXml(), 2, 1},
+        new Object[] {getValidReceiversXml(), 1, 1},
+        new Object[] {getNoReceiversXml(), 0, 0});
+  }
 }
diff --git a/geode-junit/src/main/java/org/apache/geode/test/dunit/standalone/VersionManager.java b/geode-junit/src/main/java/org/apache/geode/test/dunit/standalone/VersionManager.java
index b711193..942a939 100755
--- a/geode-junit/src/main/java/org/apache/geode/test/dunit/standalone/VersionManager.java
+++ b/geode-junit/src/main/java/org/apache/geode/test/dunit/standalone/VersionManager.java
@@ -39,6 +39,7 @@ public class VersionManager {
   public static final String GEODE_110 = "110";
   public static final String GEODE_120 = "120";
   public static final String GEODE_130 = "130";
+  public static final String GEODE_140 = "140";
 
   private static VersionManager instance;
 
diff --git a/geode-wan/src/upgradeTest/java/org/apache/geode/cache/wan/WANRollingUpgradeDUnitTest.java b/geode-wan/src/upgradeTest/java/org/apache/geode/cache/wan/WANRollingUpgradeDUnitTest.java
index e9c55ba..3263237 100644
--- a/geode-wan/src/upgradeTest/java/org/apache/geode/cache/wan/WANRollingUpgradeDUnitTest.java
+++ b/geode-wan/src/upgradeTest/java/org/apache/geode/cache/wan/WANRollingUpgradeDUnitTest.java
@@ -23,6 +23,7 @@ import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
 import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL;
 import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
 import static org.apache.geode.distributed.ConfigurationProperties.REMOTE_LOCATORS;
+import static org.apache.geode.distributed.ConfigurationProperties.USE_CLUSTER_CONFIGURATION;
 import static org.junit.Assert.assertEquals;
 
 import java.io.IOException;
@@ -72,7 +73,7 @@ import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactor
 @Parameterized.UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
 public abstract class WANRollingUpgradeDUnitTest extends JUnit4CacheTestCase {
   @Parameterized.Parameters(name = "from_v{0}")
-  public static Collection<String> data() {
+  public static Collection data() {
     List<String> result = VersionManager.getInstance().getVersionsWithoutCurrent();
     if (result.size() < 1) {
       throw new RuntimeException("No older versions of Geode were found to test against");
@@ -89,9 +90,16 @@ public abstract class WANRollingUpgradeDUnitTest extends JUnit4CacheTestCase {
   @Rule
   public transient GfshCommandRule gfsh = new GfshCommandRule();
 
-  public void startLocator(int port, int distributedSystemId, String locators,
+  void startLocator(int port, int distributedSystemId, String locators,
       String remoteLocators) throws IOException {
-    Properties props = getLocatorProperties(distributedSystemId, locators, remoteLocators);
+    startLocator(port, distributedSystemId, locators,
+        remoteLocators, false);
+  }
+
+  void startLocator(int port, int distributedSystemId, String locators,
+      String remoteLocators, boolean enableClusterConfiguration) throws IOException {
+    Properties props = getLocatorProperties(distributedSystemId, locators, remoteLocators,
+        enableClusterConfiguration);
     Locator.startLocatorAndDS(port, null, props);
   }
 
@@ -108,13 +116,22 @@ public abstract class WANRollingUpgradeDUnitTest extends JUnit4CacheTestCase {
 
   private Properties getLocatorProperties(int distributedSystemId, String locators,
       String remoteLocators) {
+    return getLocatorProperties(distributedSystemId, locators,
+        remoteLocators, false);
+  }
+
+
+  private Properties getLocatorProperties(int distributedSystemId, String locators,
+      String remoteLocators, boolean enableClusterConfiguration) {
     Properties props = new Properties();
     props.setProperty(MCAST_PORT, "0");
     props.setProperty(DISTRIBUTED_SYSTEM_ID, String.valueOf(distributedSystemId));
     props.setProperty(LOCATORS, locators);
-    props.setProperty(REMOTE_LOCATORS, remoteLocators);
+    if (remoteLocators != null) {
+      props.setProperty(REMOTE_LOCATORS, remoteLocators);
+    }
     props.setProperty(LOG_LEVEL, DUnitLauncher.logLevel);
-    props.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
+    props.setProperty(ENABLE_CLUSTER_CONFIGURATION, String.valueOf(enableClusterConfiguration));
     return props;
   }
 
@@ -124,9 +141,16 @@ public abstract class WANRollingUpgradeDUnitTest extends JUnit4CacheTestCase {
 
   VM rollLocatorToCurrent(VM rollLocator, int port, int distributedSystemId,
       String locators, String remoteLocators) {
+    return rollLocatorToCurrent(rollLocator, port, distributedSystemId,
+        locators, remoteLocators, false);
+  }
+
+  VM rollLocatorToCurrent(VM rollLocator, int port, int distributedSystemId,
+      String locators, String remoteLocators, boolean enableClusterConfiguration) {
     rollLocator.invoke(() -> stopLocator());
     VM newLocator = Host.getHost(0).getVM(VersionManager.CURRENT_VERSION, rollLocator.getId());
-    newLocator.invoke(() -> startLocator(port, distributedSystemId, locators, remoteLocators));
+    newLocator.invoke(() -> startLocator(port, distributedSystemId, locators, remoteLocators,
+        enableClusterConfiguration));
     return newLocator;
   }
 
@@ -219,7 +243,14 @@ public abstract class WANRollingUpgradeDUnitTest extends JUnit4CacheTestCase {
   }
 
   public void createCache(String locators) {
+    createCache(locators, false, false);
+  }
+
+  public void createCache(String locators, boolean enableClusterConfiguration,
+      boolean useClusterConfiguration) {
     Properties props = new Properties();
+    props.setProperty(ENABLE_CLUSTER_CONFIGURATION, String.valueOf(enableClusterConfiguration));
+    props.setProperty(USE_CLUSTER_CONFIGURATION, String.valueOf(useClusterConfiguration));
     props.setProperty(MCAST_PORT, "0");
     props.setProperty(LOCATORS, locators);
     props.setProperty(LOG_LEVEL, DUnitLauncher.logLevel);
diff --git a/geode-wan/src/upgradeTest/java/org/apache/geode/cache/wan/WANRollingUpgradeMultipleReceiversDefinedInClusterConfiguration.java b/geode-wan/src/upgradeTest/java/org/apache/geode/cache/wan/WANRollingUpgradeMultipleReceiversDefinedInClusterConfiguration.java
new file mode 100644
index 0000000..aecd3e5
--- /dev/null
+++ b/geode-wan/src/upgradeTest/java/org/apache/geode/cache/wan/WANRollingUpgradeMultipleReceiversDefinedInClusterConfiguration.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.cache.wan;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.awaitility.Awaitility;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import org.apache.geode.cache.CacheFactory;
+import org.apache.geode.cache.Region;
+import org.apache.geode.distributed.ConfigurationPersistenceService;
+import org.apache.geode.distributed.internal.InternalConfigurationPersistenceService;
+import org.apache.geode.distributed.internal.InternalLocator;
+import org.apache.geode.internal.AvailablePort;
+import org.apache.geode.internal.cache.xmlcache.CacheCreation;
+import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator;
+import org.apache.geode.management.internal.configuration.domain.Configuration;
+import org.apache.geode.management.internal.configuration.utils.XmlUtils;
+import org.apache.geode.test.dunit.DistributedTestUtils;
+import org.apache.geode.test.dunit.Host;
+import org.apache.geode.test.dunit.NetworkUtils;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.standalone.VersionManager;
+
+public class WANRollingUpgradeMultipleReceiversDefinedInClusterConfiguration
+    extends WANRollingUpgradeDUnitTest {
+
+  @Parameterized.Parameter(1)
+  public List<Attribute> attributes;
+
+  @Parameterized.Parameter(2)
+  public int expectedReceiverElements;
+
+  @Parameterized.Parameter(3)
+  public int expectedReceivers;
+
+  @Parameterized.Parameters(name = "from_v{0}; attributes={1}; expectedReceiverCount={2}")
+  public static Collection data() {
+    // Get initial versions to test against
+    List<String> versions = getVersionsToTest();
+
+    // Build up a list of version->attributes->expectedReceivers
+    List<Object[]> result = new ArrayList<>();
+    versions.forEach(version -> {
+      // Add a case for hostname-for-senders
+      addReceiversWithHostNameForSenders(result, version);
+
+      // Add a case for bind-address
+      addReceiversWithBindAddresses(result, version);
+
+      // Add a case for multiple receivers with default attributes
+      addMultipleReceiversWithDefaultAttributes(result, version);
+
+      // Add a case for single receiver with default bind-address
+      addSingleReceiverWithDefaultBindAddress(result, version);
+
+      // Add a case for single receiver with default attributes
+      addSingleReceiverWithDefaultAttributes(result, version);
+    });
+
+    System.out.println("running against these versions and attributes: "
+        + result.stream().map(entry -> Arrays.toString(entry)).collect(
+            Collectors.joining(", ")));
+    return result;
+  }
+
+  private static List<String> getVersionsToTest() {
+    // There is no need to test old versions beyond 130. Individual member configuration is not
+    // saved in cluster configuration and multiple receivers are not supported starting in 140.
+    // Note: This comparison works because '130' < '140'.
+    List<String> result = VersionManager.getInstance().getVersionsWithoutCurrent();
+    result.removeIf(version -> (version.compareTo(VersionManager.GEODE_140) >= 0));
+    if (result.size() < 1) {
+      throw new RuntimeException("No older versions of Geode were found to test against");
+    }
+    return result;
+  }
+
+  private static void addReceiversWithHostNameForSenders(List<Object[]> result, String version) {
+    List<Attribute> attributes = new ArrayList<>();
+    attributes.add(new Attribute("hostname-for-senders", "121.21.21.21"));
+    attributes.add(new Attribute("hostname-for-senders", "121.21.21.22"));
+    result.add(new Object[] {version, attributes, 2, 0});
+  }
+
+  private static void addReceiversWithBindAddresses(List<Object[]> result, String version) {
+    List<Attribute> attributes = new ArrayList<>();
+    attributes.add(new Attribute("bind-address", "121.21.21.21"));
+    attributes.add(new Attribute("bind-address", "121.21.21.22"));
+    result.add(new Object[] {version, attributes, 2, 0});
+  }
+
+  private static void addMultipleReceiversWithDefaultAttributes(List<Object[]> result,
+      String version) {
+    List<Attribute> attributes = new ArrayList<>();
+    attributes.add(Attribute.DEFAULT);
+    attributes.add(Attribute.DEFAULT);
+    result.add(new Object[] {version, attributes, 2, 1});
+  }
+
+  private static void addSingleReceiverWithDefaultAttributes(List<Object[]> result,
+      String version) {
+    List<Attribute> attributes = new ArrayList<>();
+    attributes.add(Attribute.DEFAULT);
+    result.add(new Object[] {version, attributes, 1, 1});
+  }
+
+  private static void addSingleReceiverWithDefaultBindAddress(List<Object[]> result,
+      String version) {
+    List<Attribute> attributes = new ArrayList<>();
+    attributes.add(new Attribute("bind-address", "0.0.0.0"));
+    result.add(new Object[] {version, attributes, 1, 1});
+  }
+
+  @Test
+  public void testMultipleReceiversRemovedDuringRoll() throws Exception {
+    // Get old locator properties
+    VM locator = Host.getHost(0).getVM(oldVersion, 0);
+    String hostName = NetworkUtils.getServerHostName();
+    final int locatorPort = AvailablePort.getRandomAvailablePort(AvailablePort.SOCKET);
+    DistributedTestUtils.deleteLocatorStateFile(locatorPort);
+    final String locators = hostName + "[" + locatorPort + "]";
+
+    // Start old locator
+    locator.invoke(() -> startLocator(locatorPort, 0,
+        locators, null, true));
+
+    // Wait for configuration configuration to be ready.
+    locator.invoke(
+        () -> Awaitility.await().atMost(65, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS)
+            .until(() -> assertThat(
+                InternalLocator.getLocator().isSharedConfigurationRunning()).isTrue()));
+
+    // Add cluster configuration elements containing multiple receivers
+    locator.invoke(
+        () -> addMultipleGatewayReceiverElementsToClusterConfiguration());
+
+    // Roll old locator to current
+    rollLocatorToCurrent(locator, locatorPort, 0, locators,
+        null, true);
+
+    // Verify cluster configuration contains expected number of receivers
+    locator.invoke(() -> verifyGatewayReceiverClusterConfigurationElements());
+
+    // Start member in current version with cluster configuration enabled
+    VM server = Host.getHost(0).getVM(VersionManager.CURRENT_VERSION, 1);
+    server.invoke(() -> createCache(locators, true, true));
+
+    // Verify member has expected number of receivers
+    server.invoke(() -> verifyGatewayReceivers());
+  }
+
+  private void addMultipleGatewayReceiverElementsToClusterConfiguration()
+      throws Exception {
+    // Create empty xml document
+    CacheCreation creation = new CacheCreation();
+    final StringWriter stringWriter = new StringWriter();
+    final PrintWriter printWriter = new PrintWriter(stringWriter);
+    CacheXmlGenerator.generate(creation, printWriter, true, false, false);
+    printWriter.close();
+    String baseXml = stringWriter.toString();
+    Document document = XmlUtils.createDocumentFromXml(baseXml);
+
+    // Add gateway-receiver for each attribute
+    for (Attribute attribute : attributes) {
+      Node rootNode = document.getDocumentElement();
+      Element receiverElement = document.createElement("gateway-receiver");
+      if (!attribute.name.equals("default")) {
+        receiverElement.setAttribute(attribute.name, attribute.value);
+      }
+      rootNode.appendChild(receiverElement);
+    }
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength())
+        .isEqualTo(expectedReceiverElements);
+
+    // Get configuration region
+    Region<String, Configuration> configurationRegion = CacheFactory.getAnyInstance().getRegion(
+        InternalConfigurationPersistenceService.CONFIG_REGION_NAME);
+
+    // Create a configuration and put into the configuration region
+    Configuration configuration = new Configuration(ConfigurationPersistenceService.CLUSTER_CONFIG);
+    configuration.setCacheXmlContent(XmlUtils.prettyXml(document));
+    configurationRegion.put(ConfigurationPersistenceService.CLUSTER_CONFIG, configuration);
+  }
+
+  private void verifyGatewayReceiverClusterConfigurationElements() throws Exception {
+    // Get configuration region
+    Region<String, Configuration> configurationRegion = CacheFactory.getAnyInstance().getRegion(
+        InternalConfigurationPersistenceService.CONFIG_REGION_NAME);
+
+    // Get the configuration from the region
+    Configuration configuration =
+        configurationRegion.get(ConfigurationPersistenceService.CLUSTER_CONFIG);
+
+    // Verify the configuration contains no gateway-receiver elements
+    Document document = XmlUtils.createDocumentFromXml(configuration.getCacheXmlContent());
+    assertThat(document.getElementsByTagName("gateway-receiver").getLength())
+        .isEqualTo(expectedReceivers);
+  }
+
+  private void verifyGatewayReceivers() {
+    assertThat(CacheFactory.getAnyInstance().getGatewayReceivers().size())
+        .isEqualTo(expectedReceivers);
+  }
+
+  private static class Attribute implements Serializable {
+
+    private String name;
+
+    private String value;
+
+    private static final Attribute DEFAULT = new Attribute("default", "");
+
+    Attribute(String name, String value) {
+      this.name = name;
+      this.value = value;
+    }
+
+    public String toString() {
+      return new StringBuilder()
+          .append(this.name)
+          .append("=")
+          .append(this.value)
+          .toString();
+    }
+  }
+}