You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ji...@apache.org on 2020/10/28 19:14:49 UTC

[geode] 22/23: GEODE-7845: Adding a cleaner simpler test. (#5622)

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

jinmeiliao pushed a commit to branch feature/GEODE-7665
in repository https://gitbox.apache.org/repos/asf/geode.git

commit c7f7d24203b0bc892cafbfbe8443cebc5270f32f
Author: mhansonp <ha...@vmware.com>
AuthorDate: Tue Oct 13 17:09:03 2020 -0700

    GEODE-7845: Adding a cleaner simpler test. (#5622)
    
    - Changed the test for ServerVersionMismatchException to be more readable.
---
 ...ionRegionClearMixedServerPartitionedRegion.java | 412 ---------------------
 ...ePartitionRegionClearServerVersionMismatch.java | 174 +++++++++
 2 files changed, 174 insertions(+), 412 deletions(-)

diff --git a/geode-core/src/upgradeTest/java/org/apache/geode/internal/cache/rollingupgrade/RollingUpgradePartitionRegionClearMixedServerPartitionedRegion.java b/geode-core/src/upgradeTest/java/org/apache/geode/internal/cache/rollingupgrade/RollingUpgradePartitionRegionClearMixedServerPartitionedRegion.java
deleted file mode 100644
index bfcd651..0000000
--- a/geode-core/src/upgradeTest/java/org/apache/geode/internal/cache/rollingupgrade/RollingUpgradePartitionRegionClearMixedServerPartitionedRegion.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * 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.internal.cache.rollingupgrade;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.catchThrowable;
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Properties;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.logging.log4j.Logger;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-import org.junit.runners.Parameterized.UseParametersRunnerFactory;
-
-import org.apache.geode.cache.Cache;
-import org.apache.geode.cache.CacheFactory;
-import org.apache.geode.cache.GemFireCache;
-import org.apache.geode.cache.Region;
-import org.apache.geode.cache.RegionFactory;
-import org.apache.geode.cache.RegionShortcut;
-import org.apache.geode.cache.ServerVersionMismatchException;
-import org.apache.geode.cache.client.ClientCache;
-import org.apache.geode.cache.client.ClientCacheFactory;
-import org.apache.geode.cache.client.ClientRegionShortcut;
-import org.apache.geode.cache.client.ServerOperationException;
-import org.apache.geode.cache.server.CacheServer;
-import org.apache.geode.distributed.DistributedSystem;
-import org.apache.geode.distributed.internal.DistributionConfig;
-import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
-import org.apache.geode.internal.AvailablePortHelper;
-import org.apache.geode.internal.cache.GemFireCacheImpl;
-import org.apache.geode.internal.cache.PartitionedRegion;
-import org.apache.geode.logging.internal.log4j.api.LogService;
-import org.apache.geode.test.dunit.DistributedTestUtils;
-import org.apache.geode.test.dunit.Host;
-import org.apache.geode.test.dunit.IgnoredException;
-import org.apache.geode.test.dunit.Invoke;
-import org.apache.geode.test.dunit.NetworkUtils;
-import org.apache.geode.test.dunit.VM;
-import org.apache.geode.test.dunit.internal.DUnitLauncher;
-import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase;
-import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactory;
-import org.apache.geode.test.version.VersionManager;
-
-@RunWith(Parameterized.class)
-@UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
-public class RollingUpgradePartitionRegionClearMixedServerPartitionedRegion
-    extends JUnit4DistributedTestCase {
-
-  protected static final Logger logger = LogService.getLogger();
-  protected static GemFireCache cache;
-  protected static ClientCache clientcache;
-
-  @Parameter
-  public String oldVersion;
-
-  @Parameters(name = "from_v{0}")
-  public static Collection<String> data() {
-    List<String> result = VersionManager.getInstance().getVersionsWithoutCurrent();
-    if (result.size() < 1) {
-      throw new RuntimeException("No older versions of Geode were found to test against");
-    } else {
-      System.out.println("running against these versions: " + result);
-    }
-    return result;
-  }
-
-  @Test
-  public void testPutAndGetMixedServerPartitionedRegion() throws Exception {
-    doTestPutAndGetMixedServers(oldVersion);
-  }
-
-  /**
-   * This test starts up multiple servers from the current code base and multiple servers from the
-   * old version and executes puts and gets on a new server and old server and verifies that the
-   * results are present. Note that the puts have overlapping region keys just to test new puts and
-   * replaces
-   */
-  void doTestPutAndGetMixedServers(String oldVersion)
-      throws Exception {
-    VM currentServer1 = VM.getVM(VersionManager.CURRENT_VERSION, 0);
-    VM oldServerAndLocator = VM.getVM(oldVersion, 1);
-    VM currentServer2 = VM.getVM(VersionManager.CURRENT_VERSION, 2);
-    VM oldServer2 = VM.getVM(oldVersion, 3);
-
-    String regionName = "aRegion";
-
-    final String serverHostName = NetworkUtils.getServerHostName();
-    final int port = AvailablePortHelper.getRandomAvailableTCPPort();
-    oldServerAndLocator.invoke(() -> DistributedTestUtils.deleteLocatorStateFile(port));
-    try {
-      final Properties props = getSystemProperties();
-      props.remove(DistributionConfig.LOCATORS_NAME);
-
-      // Fire up the locator and server
-      oldServerAndLocator.invoke(() -> {
-        props.put(DistributionConfig.START_LOCATOR_NAME,
-            "" + serverHostName + "[" + port + "]");
-        props.put(DistributionConfig.ENABLE_CLUSTER_CONFIGURATION_NAME, "false");
-        cache = createCache(props);
-        Thread.sleep(5000); // bug in 1.0 - cluster config service not immediately available
-      });
-
-      props.put(DistributionConfig.LOCATORS_NAME, serverHostName + "[" + port + "]");
-
-      // create the cache in all the server VMs.
-      for (VM vm : Arrays.asList(oldServer2, currentServer1, currentServer2)) {
-        vm.invoke(() -> {
-          cache = createCache(props);
-        });
-      }
-      // spin up current version servers
-      for (VM vm : Arrays.asList(currentServer1, currentServer2)) {
-        vm.invoke(
-            () -> assertVersion(cache, VersionManager.getInstance().getCurrentVersionOrdinal()));
-      }
-
-      // create region
-      for (VM vm : Arrays.asList(currentServer1, currentServer2, oldServerAndLocator, oldServer2)) {
-        vm.invoke(() -> createRegion(cache, regionName));
-      }
-
-      // put some data in the region to make sure there is something to clear.
-      putDataSerializableAndVerify(currentServer1, regionName, currentServer2, oldServerAndLocator,
-          oldServer2);
-
-      // invoke Partition Region Clear and verify we didn't touch the old servers.
-
-      currentServer1.invoke(() -> {
-        assertRegionExists(cache, regionName);
-        PartitionedRegion region = (PartitionedRegion) cache.getRegion(regionName);
-
-        Throwable thrown = catchThrowable(region::clear);
-        assertThat(thrown).isInstanceOf(ServerVersionMismatchException.class);
-
-      });
-    } finally {
-      for (VM vm : Arrays.asList(currentServer1, currentServer2, oldServerAndLocator, oldServer2)) {
-        vm.invoke(
-            () -> closeCache(RollingUpgradePartitionRegionClearMixedServerPartitionedRegion.cache));
-      }
-    }
-  }
-
-  @Test
-  public void TestClientServerGetsUnsupportedExceptionWhenPRClearInvoked() throws Exception {
-    doTestClientServerGetsUnsupportedExceptionWhenPRClearInvoked(oldVersion);
-  }
-
-  void doTestClientServerGetsUnsupportedExceptionWhenPRClearInvoked(String oldVersion)
-      throws Exception {
-
-    VM client = VM.getVM(VersionManager.CURRENT_VERSION, 0);
-    VM locator = VM.getVM(VersionManager.CURRENT_VERSION, 1);
-    VM currentServer = VM.getVM(VersionManager.CURRENT_VERSION, 2);
-    VM oldServer2 = VM.getVM(oldVersion, 3);
-
-    for (VM vm : Arrays.asList(locator, currentServer, client)) {
-      vm.invoke(() -> System.setProperty("gemfire.allow_old_members_to_join_for_testing", "true"));
-    }
-
-    String regionName = "aRegion";
-
-    final String serverHostName = NetworkUtils.getServerHostName();
-    final int port = AvailablePortHelper.getRandomAvailableTCPPort();
-    locator.invoke(() -> DistributedTestUtils.deleteLocatorStateFile(port));
-    try {
-      final Properties props = getSystemProperties();
-      props.remove(DistributionConfig.LOCATORS_NAME);
-
-      // Fire up the locator and server
-      locator.invoke(() -> {
-        props.put(DistributionConfig.START_LOCATOR_NAME,
-            "" + serverHostName + "[" + port + "]");
-        props.put(DistributionConfig.ENABLE_CLUSTER_CONFIGURATION_NAME, "false");
-        cache = createCache(props);
-      });
-
-      props.put(DistributionConfig.LOCATORS_NAME, serverHostName + "[" + port + "]");
-
-      // create the cache in all the server VMs.
-      for (VM vm : Arrays.asList(oldServer2, currentServer)) {
-        vm.invoke(() -> {
-          props.setProperty(DistributionConfig.NAME_NAME, "vm" + VM.getVMId());
-          cache = createCache(props);
-        });
-      }
-      int[] ports = AvailablePortHelper.getRandomAvailableTCPPorts(2);
-
-      oldServer2.invoke(() -> startCacheServer(cache, ports[0]));
-      currentServer.invoke(() -> startCacheServer(cache, ports[1]));
-
-      // create region
-      for (VM vm : Arrays.asList(currentServer, locator, oldServer2)) {
-        vm.invoke(() -> createRegion(cache, regionName));
-      }
-
-      // put some data in the region to make sure there is something to clear.
-      putDataSerializableAndVerify(currentServer, regionName, locator, oldServer2);
-
-      // invoke Partition Region Clear from the client and verify the exception.
-      client.invoke(() -> {
-        clientcache = new ClientCacheFactory().addPoolServer(serverHostName, ports[1]).create();
-        Region<Object, Object> clientRegion = clientcache.createClientRegionFactory(
-            ClientRegionShortcut.PROXY).create(regionName);
-
-        clientRegion.put("key", "value");
-
-        Throwable thrown = catchThrowable(clientRegion::clear);
-        assertThat(thrown).isInstanceOf(ServerOperationException.class);
-        assertThat(thrown).hasCauseInstanceOf(ServerVersionMismatchException.class);
-        ServerVersionMismatchException serverVersionMismatchException =
-            (ServerVersionMismatchException) thrown.getCause();
-        assertThat(serverVersionMismatchException.getMessage()).contains("vm3");
-      });
-
-    } finally {
-
-      for (VM vm : Arrays.asList(currentServer, locator, oldServer2)) {
-        vm.invoke(() -> closeCache(cache));
-      }
-
-      client.invoke(() -> {
-        if (cache != null && !clientcache.isClosed()) {
-          clientcache.close(false);
-        }
-      });
-    }
-  }
-
-  private String getLocatorString(int locatorPort) {
-    return getDUnitLocatorAddress() + "[" + locatorPort + "]";
-  }
-
-  public String getLocatorString(int[] locatorPorts) {
-    StringBuilder locatorString = new StringBuilder();
-    int numLocators = locatorPorts.length;
-    for (int i = 0; i < numLocators; i++) {
-      locatorString.append(getLocatorString(locatorPorts[i]));
-      if (i + 1 < numLocators) {
-        locatorString.append(",");
-      }
-    }
-    return locatorString.toString();
-  }
-
-  private Cache createCache(Properties systemProperties) {
-    systemProperties.setProperty(DistributionConfig.USE_CLUSTER_CONFIGURATION_NAME, "false");
-    if (VersionManager.getInstance().getCurrentVersionOrdinal() < 75) {
-      systemProperties.remove("validate-serializable-objects");
-      systemProperties.remove("serializable-object-filter");
-    }
-    CacheFactory cf = new CacheFactory(systemProperties);
-    return cf.create();
-  }
-
-  private void startCacheServer(GemFireCache cache, int port) throws Exception {
-    CacheServer cacheServer = ((GemFireCacheImpl) cache).addCacheServer();
-    cacheServer.setPort(port);
-    cacheServer.start();
-  }
-
-  protected void assertRegionExists(GemFireCache cache, String regionName) {
-    Region<Object, Object> region = cache.getRegion(regionName);
-    if (region == null) {
-      throw new Error("Region: " + regionName + " does not exist");
-    }
-  }
-
-  private void assertEntryExists(GemFireCache cache, String regionName) {
-    assertRegionExists(cache, regionName);
-    Region<Object, Object> region = cache.getRegion(regionName);
-    for (int i = 0; i < 10; i++) {
-      String key = "" + i;
-      Object regionValue = region.get(key);
-      assertThat(regionValue).describedAs("Entry for key:" + key + " does not exist").isNotNull();
-    }
-  }
-
-  public void put(GemFireCache cache, String regionName, Object key, Object value) {
-    Region<Object, Object> region = cache.getRegion(regionName);
-    System.out.println(regionName + ".put(" + key + "," + value + ")");
-    Object result = region.put(key, value);
-    System.out.println("returned " + result);
-  }
-
-  private void createRegion(GemFireCache cache, String regionName) {
-    RegionFactory<Object, Object> rf = ((GemFireCacheImpl) cache).createRegionFactory(
-        RegionShortcut.PARTITION);
-    System.out.println("created region " + rf.create(regionName));
-  }
-
-  void assertVersion(GemFireCache cache, short ordinal) {
-    DistributedSystem system = cache.getDistributedSystem();
-    int thisOrdinal =
-        ((InternalDistributedMember) system.getDistributedMember()).getVersion()
-            .ordinal();
-    if (ordinal != thisOrdinal) {
-      throw new Error(
-          "Version ordinal:" + thisOrdinal + " was not the expected ordinal of:" + ordinal);
-    }
-  }
-
-  private void closeCache(GemFireCache cache) {
-    if (cache == null) {
-      return;
-    }
-    boolean cacheClosed = cache.isClosed();
-    if (!cacheClosed) {
-      List<CacheServer> servers = ((Cache) cache).getCacheServers();
-      for (CacheServer server : servers) {
-        server.stop();
-      }
-      cache.close();
-    }
-  }
-
-  /**
-   * Get the port that the standard dunit locator is listening on.
-   *
-   */
-  private String getDUnitLocatorAddress() {
-    return Host.getHost(0).getHostName();
-  }
-
-  private void deleteVMFiles() {
-    System.out.println("deleting files in vm" + VM.getVMId());
-    File pwd = new File(".");
-    for (File entry : pwd.listFiles()) {
-      try {
-        if (entry.isDirectory()) {
-          FileUtils.deleteDirectory(entry);
-        } else {
-          if (!entry.delete()) {
-            System.out.println("Could not delete " + entry);
-          }
-        }
-      } catch (Exception e) {
-        System.out.println("Could not delete " + entry + ": " + e.getMessage());
-      }
-    }
-  }
-
-  @Override
-  public void postSetUp() {
-    Invoke.invokeInEveryVM("delete files", this::deleteVMFiles);
-    IgnoredException.addIgnoredException(
-        "cluster configuration service not available|ConflictingPersistentDataException");
-  }
-
-
-  void putDataSerializableAndVerify(VM putter, String regionName,
-      VM... vms) throws Exception {
-    for (int i = 0; i < 10; i++) {
-      Class aClass = Thread.currentThread().getContextClassLoader()
-          .loadClass("org.apache.geode.cache.ExpirationAttributes");
-      Constructor constructor = aClass.getConstructor(int.class);
-      Object testDataSerializable = constructor.newInstance(i);
-      int finalI = i;
-      putter.invoke(() -> put(cache, regionName, "" + finalI, testDataSerializable));
-    }
-
-    // verify present in others
-    for (VM vm : vms) {
-      vm.invoke(() -> assertEntryExists(cache, regionName));
-    }
-  }
-
-  public Properties getSystemProperties() {
-    Properties props = DistributedTestUtils.getAllDistributedSystemProperties(new Properties());
-    props.remove("disable-auto-reconnect");
-    props.put(DistributionConfig.ENABLE_CLUSTER_CONFIGURATION_NAME, "true");
-    props.put(DistributionConfig.USE_CLUSTER_CONFIGURATION_NAME, "false");
-    props.remove(DistributionConfig.LOAD_CLUSTER_CONFIG_FROM_DIR_NAME);
-    props.remove(DistributionConfig.OFF_HEAP_MEMORY_SIZE_NAME);
-    props.remove(DistributionConfig.LOCK_MEMORY_NAME);
-    return props;
-  }
-
-  public Properties getSystemProperties(int[] locatorPorts) {
-    Properties props = new Properties();
-    String locatorString = getLocatorString(locatorPorts);
-    props.setProperty("locators", locatorString);
-    props.setProperty("mcast-port", "0");
-    props.put(DistributionConfig.ENABLE_CLUSTER_CONFIGURATION_NAME, "true");
-    props.put(DistributionConfig.USE_CLUSTER_CONFIGURATION_NAME, "false");
-    props.remove(DistributionConfig.LOAD_CLUSTER_CONFIG_FROM_DIR_NAME);
-    props.setProperty(DistributionConfig.LOG_LEVEL_NAME, DUnitLauncher.logLevel);
-    return props;
-  }
-}
diff --git a/geode-core/src/upgradeTest/java/org/apache/geode/internal/cache/rollingupgrade/RollingUpgradePartitionRegionClearServerVersionMismatch.java b/geode-core/src/upgradeTest/java/org/apache/geode/internal/cache/rollingupgrade/RollingUpgradePartitionRegionClearServerVersionMismatch.java
new file mode 100644
index 0000000..144ea38
--- /dev/null
+++ b/geode-core/src/upgradeTest/java/org/apache/geode/internal/cache/rollingupgrade/RollingUpgradePartitionRegionClearServerVersionMismatch.java
@@ -0,0 +1,174 @@
+/*
+ * 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.internal.cache.rollingupgrade;
+
+import static org.apache.geode.test.dunit.rules.ClusterStartupRule.getCache;
+import static org.apache.geode.test.dunit.rules.ClusterStartupRule.getClientCache;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.ServerVersionMismatchException;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientRegionFactory;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.cache.client.ServerOperationException;
+import org.apache.geode.distributed.internal.DistributionConfig;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.rules.ClientVM;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactory;
+import org.apache.geode.test.version.VersionManager;
+
+/**
+ * This test class exists to test the ServerVersionMismatchException
+ * A ServerVersionMismatchException is thrown when a cluster has a server that is previous to
+ * version 1.14.0 which doesn't support the Partitioned Region Clear feature.
+ *
+ * When the exception is thrown it is expected to contain the members that have the bad version,
+ * the version number necessary, and the feature that is not supported.
+ */
+
+
+@RunWith(Parameterized.class)
+@Parameterized.UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
+public class RollingUpgradePartitionRegionClearServerVersionMismatch {
+
+  @Rule
+  public ClusterStartupRule cluster = new ClusterStartupRule();
+
+  @Parameterized.Parameter
+  public String oldVersion;
+
+  @Parameterized.Parameters(name = "from_v{0}")
+  public static Collection<String> data() {
+    List<String> result = VersionManager.getInstance().getVersionsWithoutCurrent();
+    if (result.size() < 1) {
+      throw new RuntimeException("No older versions of Geode were found to test against");
+    } else {
+      System.out.println("running against these versions: " + result);
+    }
+    return result;
+  }
+
+  // This is the message that we are expected to be in the exception in both tests below.
+  private static final String expectedMessage =
+      "A server's [server-2] version was too old (< GEODE 1.14.0) for : Partitioned Region Clear";
+
+  private MemberVM locator;
+  private MemberVM serverNew;
+
+  @Before
+  public void before() {
+    locator = cluster.startLocatorVM(0,
+        l -> l.withSystemProperty("gemfire.allow_old_members_to_join_for_testing", "true")
+            .withProperty(DistributionConfig.ENABLE_CLUSTER_CONFIGURATION_NAME, "false"));
+    final int locatorPort = locator.getPort();
+
+    serverNew = cluster.startServerVM(1, locatorPort);
+    MemberVM serverOld =
+        cluster.startServerVM(2, oldVersion, s -> s.withConnectionToLocator(locatorPort));
+
+    MemberVM.invokeInEveryMember(() -> {
+      Cache cache = getCache();
+      assertThat(cache).isNotNull();
+      getCache().createRegionFactory(RegionShortcut.PARTITION).create("regionA");
+    }, serverNew, serverOld);
+
+    // Put in some boiler plate data for region clear
+    serverNew.invoke(() -> {
+      Cache cache = getCache();
+      assertThat(cache).isNotNull();
+
+      Region<String, String> region = cache.getRegion("regionA");
+      region.put("A", "ValueA");
+      region.put("B", "ValueB");
+    });
+
+  }
+
+  /**
+   * testClient_ServerVersionMismatchException - validates that when a client invokes a partitioned
+   * region clear on a cluster where one server is running an unsupported version for this feature
+   * we return a ServerVersionMismatchException
+   */
+  @Test
+  public void testClient_ServerVersionMismatchException() throws Exception {
+    IgnoredException.addIgnoredException(ServerOperationException.class);
+    final int locatorPort = locator.getPort();
+    // Get a client VM
+    ClientVM clientVM = cluster.startClientVM(3, c -> c.withLocatorConnection(locatorPort));
+
+    clientVM.invoke(() -> {
+      // Validate we have a cache and region
+      ClientCache clientCache = getClientCache();
+      assertThat(clientCache).isNotNull();
+
+      ClientRegionFactory<String, String> clientRegionFactory =
+          clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY);
+      Region<String, String> region = clientRegionFactory.create("regionA");
+      assertThat(region).isNotNull();
+
+      // Validate that we get a ServerVersionMismatchException wrapped in a ServerOperationException
+      Throwable thrown = catchThrowable(region::clear);
+      assertThat(thrown).isInstanceOf(ServerOperationException.class);
+      assertThat(thrown).hasCauseInstanceOf(ServerVersionMismatchException.class);
+
+      // Validate that the message is exactly as we expect it.
+      ServerVersionMismatchException serverVersionMismatchException =
+          (ServerVersionMismatchException) thrown.getCause();
+      assertThat(serverVersionMismatchException.getMessage()).isEqualTo(expectedMessage);
+    });
+  }
+
+  /**
+   * testServer_ServerVersionMismatchException - validates that when a partitioned region clear is
+   * invoked on a cluster where one server is running an unsupported version for this feature we
+   * return a ServerVersionMismatchException
+   */
+  @Test
+  public void testServer_ServerVersionMismatchException() {
+    IgnoredException.addIgnoredException(ServerOperationException.class);
+
+    serverNew.invoke(() -> {
+      // Validate we have a cache and region
+      Cache cache = getCache();
+      assertThat(cache).isNotNull();
+
+      Region<String, String> region = cache.getRegion("regionA");
+      assertThat(region).isNotNull();
+
+      // Validate that the message is exactly as we expect it.
+      assertThatThrownBy(region::clear).isInstanceOf(ServerVersionMismatchException.class)
+          .hasMessage(expectedMessage);
+
+      assertThat(region.get("A")).isEqualTo("ValueA");
+      assertThat(region.get("B")).isEqualTo("ValueB");
+    });
+  }
+}