/*
 * 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.management;

import static org.junit.Assert.*;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.PartitionAttributesFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.control.ResourceManager;
import org.apache.geode.cache.partition.PartitionMemberInfo;
import org.apache.geode.cache.partition.PartitionRegionHelper;
import org.apache.geode.cache.partition.PartitionRegionInfo;
import org.apache.geode.cache.query.QueryException;
import org.apache.geode.cache.query.QueryInvalidException;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
import org.apache.geode.internal.cache.BucketAdvisor;
import org.apache.geode.internal.cache.BucketRegion;
import org.apache.geode.internal.cache.EntrySnapshot;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.internal.cache.PartitionedRegionDataStore;
import org.apache.geode.internal.cache.PartitionedRegionDataStore.CreateBucketResult;
import org.apache.geode.internal.cache.control.InternalResourceManager;
import org.apache.geode.internal.cache.control.InternalResourceManager.ResourceType;
import org.apache.geode.internal.cache.control.ResourceEvent;
import org.apache.geode.internal.cache.control.ResourceListener;
import org.apache.geode.internal.cache.partitioned.BecomePrimaryBucketMessage;
import org.apache.geode.internal.cache.partitioned.BecomePrimaryBucketMessage.BecomePrimaryBucketResponse;
import org.apache.geode.internal.cache.partitioned.Bucket;
import org.apache.geode.internal.cache.partitioned.DeposePrimaryBucketMessage;
import org.apache.geode.internal.cache.partitioned.DeposePrimaryBucketMessage.DeposePrimaryBucketResponse;
import org.apache.geode.internal.cache.partitioned.InternalPRInfo;
import org.apache.geode.internal.cache.partitioned.InternalPartitionDetails;
import org.apache.geode.internal.cache.partitioned.PRLoad;
import org.apache.geode.internal.cache.partitioned.RemoveBucketMessage;
import org.apache.geode.internal.cache.partitioned.RemoveBucketMessage.RemoveBucketResponse;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.test.dunit.Assert;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.SerializableCallable;
import org.apache.geode.test.dunit.SerializableRunnable;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
import org.apache.geode.test.junit.categories.DistributedTest;
import org.apache.geode.test.junit.categories.FlakyTest;

/**
 * Tests org.apache.geode.cache.control.ResourceManager.
 * 
 * TODO: javadoc this test properly and cleanup the helper methods to be more flexible and
 * understandable
 */
@Category(DistributedTest.class)
public class ResourceManagerDUnitTest extends JUnit4CacheTestCase {
  private static final Logger logger = LogService.getLogger();

  private static final int SYSTEM_LISTENERS = 1;

  /**
   * Creates a cache in the controller and exercises all methods on the ResourceManager without
   * having any partitioned regions defined.
   */
  @Test
  public void testResourceManagerBasics() {
    Cache cache = getCache();

    // verify that getResourceManager works
    ResourceManager manager = cache.getResourceManager();
    assertNotNull(manager);

    // verify that getPartitionedRegionDetails returns empty set
    Set<PartitionRegionInfo> detailsSet = PartitionRegionHelper.getPartitionRegionInfo(cache);
    assertNotNull(detailsSet);
    assertEquals(Collections.emptySet(), detailsSet);

    ResourceListener listener = new ResourceListener() {
      public void onEvent(ResourceEvent event) {}
    };

    InternalResourceManager internalManager = (InternalResourceManager) manager;
    // verify that addResourceListener works
    internalManager.addResourceListener(listener);
    Set<ResourceListener> listeners =
        internalManager.getResourceListeners(ResourceType.HEAP_MEMORY);
    assertNotNull(listeners);
    assertEquals(1 + SYSTEM_LISTENERS, listeners.size());
    assertTrue(listeners.contains(listener));

    // verify that repeat adds result in only one entry of the listener
    internalManager.addResourceListener(ResourceType.HEAP_MEMORY, listener);
    listeners = internalManager.getResourceListeners(ResourceType.HEAP_MEMORY);
    assertEquals(1 + SYSTEM_LISTENERS, listeners.size());

    // verify that removeResourceListener works
    internalManager.removeResourceListener(listener);
    listeners = internalManager.getResourceListeners(ResourceType.HEAP_MEMORY);
    assertEquals(listeners.size(), SYSTEM_LISTENERS);

  }

  /**
   * Creates partitioned regions in multiple vms and fully exercises the getPartitionedRegionDetails
   * API on ResourceManager.
   */
  @Test
  public void testGetPartitionedRegionDetails() {
    // two regions
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0", getUniqueName() + "-PR-1"};
    // numBuckets config for the two regions
    final int[] numBuckets = new int[] {100, 90};
    // redundantCopies config for the two regions
    final int[] redundantCopies = new int[] {1, 0};

    // localMaxMemory config to use for three members
    final int[] localMaxMemory = new int[] {50, 100, 0};

    // bucketKeys to use for making three buckets in first PR
    final Integer[] bucketKeys =
        new Integer[] {Integer.valueOf(0), Integer.valueOf(42), Integer.valueOf(76)};

    assertEquals(0, bucketKeys[0].hashCode());
    assertEquals(42, bucketKeys[1].hashCode());
    assertEquals(76, bucketKeys[2].hashCode());

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory[0], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory[1], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(2), regionPath[0], localMaxMemory[2], numBuckets[0],
        redundantCopies[0]);

    createRegion(Host.getHost(0).getVM(0), regionPath[1], localMaxMemory[0], numBuckets[1],
        redundantCopies[1]);

    final byte[] value = new byte[1024 * 1024 * 2]; // 2 MB in size

    createBuckets(0, regionPath[0], bucketKeys, value);

    // identify the members and their config values
    final InternalDistributedMember[] members = new InternalDistributedMember[3];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    // test everything here
    for (int i = 0; i < localMaxMemory.length; i++) {
      final int vm = i;
      Host.getHost(0).getVM(vm).invoke(new SerializableRunnable() {
        public void run() {
          Set<PartitionRegionInfo> detailsSet =
              PartitionRegionHelper.getPartitionRegionInfo(getCache());
          if (vm == 0) {
            assertEquals(2, detailsSet.size());
          } else {
            assertEquals(1, detailsSet.size());
          }

          // iterate over each PartitionedRegionDetails
          for (Iterator<PartitionRegionInfo> prIter = detailsSet.iterator(); prIter.hasNext();) {
            PartitionRegionInfo details = prIter.next();
            // NOTE: getRegionPath() contains the Region.SEPARATOR + regionPath
            assertTrue("Unknown regionPath=" + details.getRegionPath(),
                details.getRegionPath().contains(regionPath[0])
                    || details.getRegionPath().contains(regionPath[1]));
            if (details.getRegionPath().contains(regionPath[0])) {
              assertEquals(numBuckets[0], details.getConfiguredBucketCount());
              assertEquals(0, details.getLowRedundancyBucketCount());
              assertEquals(redundantCopies[0], details.getConfiguredRedundantCopies());
              assertEquals(redundantCopies[0], details.getActualRedundantCopies());
              assertNull(details.getColocatedWith());
              Set<PartitionMemberInfo> memberDetails = details.getPartitionMemberInfo();
              assertNotNull(memberDetails);
              assertEquals(localMaxMemory.length - 1, memberDetails.size());

              // iterate over each PartitionMemberDetails (datastores only)
              for (Iterator<PartitionMemberInfo> mbrIter = memberDetails.iterator(); mbrIter
                  .hasNext();) {
                PartitionMemberInfo mbrDetails = mbrIter.next();
                assertNotNull(mbrDetails);
                DistributedMember mbr = mbrDetails.getDistributedMember();
                assertNotNull(mbr);
                int membersIdx = -1;
                for (int idx = 0; idx < members.length; idx++) {
                  if (mbr.equals(members[idx])) {
                    membersIdx = idx;
                  }
                }

                assertEquals(localMaxMemory[membersIdx] * (1024L * 1024L),
                    mbrDetails.getConfiguredMaxMemory());
                assertEquals(memberSizes[membersIdx], mbrDetails.getSize());
                assertEquals(memberBucketCounts[membersIdx], mbrDetails.getBucketCount());
                assertEquals(memberPrimaryCounts[membersIdx], mbrDetails.getPrimaryCount());

                if (mbr.equals(getSystem().getDistributedMember())) {
                  // PartitionMemberDetails represents the local member
                  PartitionedRegion pr =
                      (PartitionedRegion) getCache().getRegion(details.getRegionPath());
                  assertEquals(pr.getLocalMaxMemory() * (1024L * 1024L),
                      mbrDetails.getConfiguredMaxMemory());
                  PartitionedRegionDataStore ds = pr.getDataStore();
                  assertNotNull(ds);
                  assertEquals(getSize(ds), mbrDetails.getSize());
                  assertEquals(ds.getBucketsManaged(), mbrDetails.getBucketCount());
                  assertEquals(ds.getNumberOfPrimaryBucketsManaged(), mbrDetails.getPrimaryCount());
                }

              }

            } else {
              // found the other PR which has only one datastore and we know
              // this system memberId is the only entry in mbrDetails
              assertEquals(numBuckets[1], details.getConfiguredBucketCount());
              assertEquals(0, details.getLowRedundancyBucketCount());
              assertEquals(redundantCopies[1], details.getConfiguredRedundantCopies());
              assertEquals(redundantCopies[1], details.getActualRedundantCopies());
              assertNull(details.getColocatedWith());

              Set<PartitionMemberInfo> memberDetails = details.getPartitionMemberInfo();
              assertNotNull(memberDetails);
              assertEquals(1, memberDetails.size());
              PartitionMemberInfo mbrDetails = memberDetails.iterator().next();
              assertEquals(getSystem().getDistributedMember(), mbrDetails.getDistributedMember());

              PartitionedRegion pr =
                  (PartitionedRegion) getCache().getRegion(details.getRegionPath());
              assertEquals(pr.getLocalMaxMemory() * (1024L * 1024L),
                  mbrDetails.getConfiguredMaxMemory());
              PartitionedRegionDataStore ds = pr.getDataStore();
              assertNotNull(ds);
              assertEquals(getSize(ds), mbrDetails.getSize());
              assertEquals(ds.getBucketsManaged(), mbrDetails.getBucketCount());
              assertEquals(ds.getNumberOfPrimaryBucketsManaged(), mbrDetails.getPrimaryCount());

            }
          }
        }
      });
    }

    destroyRegions(0, regionPath);
  }

  /**
   * Creates partitioned regions in multiple vms and fully exercises the internal-only
   * getInternalPRDetails API on ResourceManager.
   */
  @Test
  public void testGetInternalPRDetails() {
    // two regions
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0", getUniqueName() + "-PR-1"};
    // numBuckets config for the two regions
    final int[] numBuckets = new int[] {100, 90};
    // redundantCopies config for the two regions
    final int[] redundantCopies = new int[] {1, 0};

    // localMaxMemory config to use for three members
    final int[] localMaxMemory = new int[] {50, 100, 0};

    // bucketKeys to use for making three bckets in first PR
    final Integer[] bucketKeys =
        new Integer[] {Integer.valueOf(0), Integer.valueOf(42), Integer.valueOf(76)};

    assertEquals(0, bucketKeys[0].hashCode());
    assertEquals(42, bucketKeys[1].hashCode());
    assertEquals(76, bucketKeys[2].hashCode());

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory[0], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory[1], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(2), regionPath[0], localMaxMemory[2], numBuckets[0],
        redundantCopies[0]);

    createRegion(Host.getHost(0).getVM(0), regionPath[1], localMaxMemory[0], numBuckets[1],
        redundantCopies[1]);

    final byte[] value = new byte[1024 * 1024 * 2]; // 2 MB in size

    createBuckets(0, regionPath[0], bucketKeys, value);

    // identify the members and their config values
    final InternalDistributedMember[] members = new InternalDistributedMember[3];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];
    for (int i = 0; i < members.length; i++) {
      final int vm = i;
      members[vm] =
          (InternalDistributedMember) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
            public Object call() {
              return getSystem().getDistributedMember();
            }
          });
      memberSizes[vm] = ((Long) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
        public Object call() {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);
          PartitionedRegionDataStore ds = pr.getDataStore();
          if (ds == null) {
            return Long.valueOf(0);
          } else {
            return Long.valueOf(getSize(ds));
          }
        }
      })).longValue();
      memberBucketCounts[vm] =
          ((Integer) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
            public Object call() {
              PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);
              PartitionedRegionDataStore ds = pr.getDataStore();
              if (ds == null) {
                return new Integer(0);
              } else {
                return new Integer(ds.getBucketsManaged());
              }
            }
          })).intValue();
      memberPrimaryCounts[vm] =
          ((Integer) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
            public Object call() {
              PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);
              PartitionedRegionDataStore ds = pr.getDataStore();
              if (ds == null) {
                return new Integer(0);
              } else {
                return new Integer(ds.getNumberOfPrimaryBucketsManaged());
              }
            }
          })).intValue();
    }

    // test everything here
    for (int i = 0; i < localMaxMemory.length; i++) {
      final int vm = i;
      Host.getHost(0).getVM(vm).invoke(new SerializableRunnable() {
        public void run() {
          Set<InternalPRInfo> detailsSet = new HashSet<InternalPRInfo>();
          GemFireCacheImpl cache = (GemFireCacheImpl) getCache();
          for (PartitionedRegion pr : cache.getPartitionedRegions()) {
            InternalPRInfo info = pr.getRedundancyProvider().buildPartitionedRegionInfo(true,
                cache.getInternalResourceManager().getLoadProbe());
            detailsSet.add(info);
          }
          if (vm == 0) {
            assertEquals(2, detailsSet.size());
          } else {
            assertEquals(1, detailsSet.size());
          }

          // iterate over each InternalPRDetails
          for (Iterator<InternalPRInfo> prIter = detailsSet.iterator(); prIter.hasNext();) {
            InternalPRInfo details = prIter.next();
            // NOTE: getRegionPath() contains the Region.SEPARATOR + regionPath
            assertTrue("Unknown regionPath=" + details.getRegionPath(),
                details.getRegionPath().contains(regionPath[0])
                    || details.getRegionPath().contains(regionPath[1]));
            if (details.getRegionPath().contains(regionPath[0])) {
              assertEquals(numBuckets[0], details.getConfiguredBucketCount());
              assertEquals(0, details.getLowRedundancyBucketCount());
              assertEquals(redundantCopies[0], details.getConfiguredRedundantCopies());
              assertEquals(redundantCopies[0], details.getActualRedundantCopies());
              assertNull(details.getColocatedWith());
              Set<InternalPartitionDetails> memberDetails = details.getInternalPartitionDetails();
              assertNotNull(memberDetails);
              assertEquals(localMaxMemory.length - 1, memberDetails.size());

              // iterate over each InternalPartitionDetails (datastores only)
              for (Iterator<InternalPartitionDetails> mbrIter = memberDetails.iterator(); mbrIter
                  .hasNext();) {
                InternalPartitionDetails mbrDetails = mbrIter.next();
                assertNotNull(mbrDetails);
                DistributedMember mbr = mbrDetails.getDistributedMember();
                assertNotNull(mbr);
                int membersIdx = -1;
                for (int idx = 0; idx < members.length; idx++) {
                  if (mbr.equals(members[idx])) {
                    membersIdx = idx;
                  }
                }

                assertEquals(localMaxMemory[membersIdx] * 1024 * 1024,
                    mbrDetails.getConfiguredMaxMemory());
                assertEquals(memberSizes[membersIdx], mbrDetails.getSize());
                assertEquals(memberBucketCounts[membersIdx], mbrDetails.getBucketCount());
                assertEquals(memberPrimaryCounts[membersIdx], mbrDetails.getPrimaryCount());

                PRLoad load = mbrDetails.getPRLoad();
                assertNotNull(load);
                assertEquals((float) localMaxMemory[membersIdx], load.getWeight(), 0);

                int totalBucketBytes = 0;
                int primaryCount = 0;
                for (int bid = 0; bid < numBuckets[0]; bid++) {
                  long bucketBytes = mbrDetails.getBucketSize(bid);
                  assertTrue(bucketBytes >= 0);
                  totalBucketBytes += bucketBytes;

                  // validate against the PRLoad
                  assertEquals((float) bucketBytes, load.getReadLoad(bid), 0);
                  if (load.getWriteLoad(bid) > 0) { // found a primary
                    primaryCount++;
                  }
                }
                // assertIndexDetailsEquals(memberSizes[membersIdx] * (1024* 1024),
                // totalBucketBytes);
                assertEquals(memberPrimaryCounts[membersIdx], primaryCount);

                if (mbr.equals(getSystem().getDistributedMember())) {
                  // PartitionMemberDetails represents the local member
                  PartitionedRegion pr =
                      (PartitionedRegion) getCache().getRegion(details.getRegionPath());
                  assertEquals(pr.getLocalMaxMemory() * (1024L * 1024L),
                      mbrDetails.getConfiguredMaxMemory());
                  PartitionedRegionDataStore ds = pr.getDataStore();
                  assertNotNull(ds);
                  assertEquals(getSize(ds), mbrDetails.getSize());
                  assertEquals(ds.getBucketsManaged(), mbrDetails.getBucketCount());
                  assertEquals(ds.getNumberOfPrimaryBucketsManaged(), mbrDetails.getPrimaryCount());
                }

              }

            } else {
              // found the other PR which has only one datastore and we know
              // this system memberId is the only entry in mbrDetails
              assertEquals(numBuckets[1], details.getConfiguredBucketCount());
              assertEquals(0, details.getLowRedundancyBucketCount());
              assertEquals(redundantCopies[1], details.getConfiguredRedundantCopies());
              assertEquals(redundantCopies[1], details.getActualRedundantCopies());
              assertNull(details.getColocatedWith());

              Set<PartitionMemberInfo> memberDetails = details.getPartitionMemberInfo();
              assertNotNull(memberDetails);
              assertEquals(1, memberDetails.size());
              PartitionMemberInfo mbrDetails = memberDetails.iterator().next();
              assertEquals(getSystem().getDistributedMember(), mbrDetails.getDistributedMember());

              PartitionedRegion pr =
                  (PartitionedRegion) getCache().getRegion(details.getRegionPath());
              assertEquals(pr.getLocalMaxMemory() * (1024L * 1024L),
                  mbrDetails.getConfiguredMaxMemory());
              PartitionedRegionDataStore ds = pr.getDataStore();
              assertNotNull(ds);
              assertEquals(getSize(ds), mbrDetails.getSize());
              assertEquals(ds.getBucketsManaged(), mbrDetails.getBucketCount());
              assertEquals(ds.getNumberOfPrimaryBucketsManaged(), mbrDetails.getPrimaryCount());

            }
          }
        }
      });
    }

    destroyRegions(0, regionPath);
  }

  private void createRegion(final VM whichVm, final String regionPath, final int localMaxMemory,
      final int numBuckets, final int redundantCopies, final String colocatedWith) {
    whichVm.invoke(new SerializableRunnable() {
      public void run() {
        {
          AttributesFactory factory = new AttributesFactory();
          factory.setPartitionAttributes(new PartitionAttributesFactory()
              .setLocalMaxMemory(localMaxMemory).setTotalNumBuckets(numBuckets)
              .setRedundantCopies(redundantCopies).setColocatedWith(colocatedWith).create());

          RegionAttributes attrs = factory.create();
          Region pr = createRootRegion(regionPath, attrs);
          assertNotNull(pr);
        }
      }
    });
  }

  private void createRegion(final VM whichVm, final String regionPath, final int localMaxMemory,
      final int numBuckets, final int redundantCopies) {
    createRegion(whichVm, regionPath, localMaxMemory, numBuckets, redundantCopies, null);
  }

  private void createBucket(final int vm, final String regionPath, final Integer bucketKey,
      final byte[] value) {
    createBuckets(vm, regionPath, new Integer[] {bucketKey}, value);
  }

  private void createBuckets(final int vm, final String regionPath, final Integer[] bucketKeys,
      final Object value) {
    // do some puts to create buckets
    performPuts(vm, regionPath, bucketKeys, value);
  }

  private void performPuts(final int vm, final String regionPath, final Integer[] bucketKeys,
      final Object value) {
    Host.getHost(0).getVM(vm).invoke(new SerializableRunnable() {
      public void run() {
        Region pr = getCache().getRegion(regionPath);
        for (int i = 0; i < bucketKeys.length; i++) {
          pr.put(bucketKeys[i], value);
        }
      }
    });
  }

  private void fillValidationArrays(final InternalDistributedMember[] members,
      final long[] memberSizes, final int[] memberBucketCounts, final int[] memberPrimaryCounts,
      final String regionPath) {
    for (int i = 0; i < members.length; i++) {
      final int vm = i;
      members[vm] =
          (InternalDistributedMember) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
            public Object call() {
              return getSystem().getDistributedMember();
            }
          });
      memberSizes[vm] = ((Long) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
        public Object call() {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath);
          PartitionedRegionDataStore ds = pr.getDataStore();
          if (ds == null) {
            return Long.valueOf(0);
          } else {
            return Long.valueOf(getSize(ds));
          }
        }
      })).longValue();
      memberBucketCounts[vm] =
          ((Integer) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
            public Object call() {
              PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath);
              PartitionedRegionDataStore ds = pr.getDataStore();
              if (ds == null) {
                return new Integer(0);
              } else {
                return new Integer(ds.getBucketsManaged());
              }
            }
          })).intValue();
      memberPrimaryCounts[vm] =
          ((Integer) Host.getHost(0).getVM(vm).invoke(new SerializableCallable() {
            public Object call() {
              PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath);
              PartitionedRegionDataStore ds = pr.getDataStore();
              if (ds == null) {
                return new Integer(0);
              } else {
                return new Integer(ds.getNumberOfPrimaryBucketsManaged());
              }
            }
          })).intValue();
    }
  }

  private long getSize(PartitionedRegionDataStore ds) {
    long size = 0;
    int totalNumBuckets = ds.getPartitionedRegion().getPartitionAttributes().getTotalNumBuckets();
    for (int i = 0; i < totalNumBuckets; i++) {
      size += ds.getBucketSize(i);
    }
    return size;
  }

  private void destroyRegions(final int vm, final String[] regionPath) {
    // destroy both PRs (note that vm 0 created both)
    Host.getHost(0).getVM(vm).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          Region pr = getCache().getRegion(regionPath[i]);
          pr.destroyRegion();
        }
      }
    });
  }

  @Test
  public void testDeposePrimaryBucketMessage() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0"};
    final int[] numBuckets = new int[] {1};
    final int[] redundantCopies = new int[] {1};

    // localMaxMemory config to use for two members
    final int[] localMaxMemory = new int[] {100, 100};

    // bucketKeys to use for making one bucket
    final Integer[] bucketKeys = new Integer[] {Integer.valueOf(0)};

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory[0], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory[1], numBuckets[0],
        redundantCopies[0]);

    final byte[] value = new byte[1]; // 2 MB in size

    createBucket(0, regionPath[0], bucketKeys[0], value);

    // identify the members and their config values
    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    boolean deposedPrimary =
        ((Boolean) Host.getHost(0).getVM(otherVM).invoke(new SerializableCallable() {
          public Object call() {
            PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

            InternalDistributedMember primaryMember =
                pr.getRegionAdvisor().getBucketAdvisor(0).getPrimary();

            DeposePrimaryBucketResponse response =
                DeposePrimaryBucketMessage.send(primaryMember, pr, 0);
            if (response != null) {
              response.waitForRepliesUninterruptibly();
              return true;
            } else {
              return Boolean.FALSE;
            }
          }
        })).booleanValue();

    assertTrue(deposedPrimary);
  }

  @Test
  public void testBecomePrimaryBucketMessage() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0"};
    final int[] numBuckets = new int[] {1};
    final int[] redundantCopies = new int[] {1};

    // localMaxMemory config to use for two members
    final int[] localMaxMemory = new int[] {100, 100, 0};

    // bucketKeys to use for making one bucket
    final Integer[] bucketKeys = new Integer[] {Integer.valueOf(0)};

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory[0], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory[1], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(2), regionPath[0], localMaxMemory[2], numBuckets[0],
        redundantCopies[0]);

    final byte[] value = new byte[1]; // 2 MB in size

    createBucket(0, regionPath[0], bucketKeys[0], value);

    // identify the members and their config values
    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    int accessorVM = 2;
    assertTrue(accessorVM != primaryVM && accessorVM != otherVM);

    final int finalOtherVM = otherVM;

    boolean becamePrimary =
        ((Boolean) Host.getHost(0).getVM(accessorVM).invoke(new SerializableCallable() {
          public Object call() {
            PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

            BecomePrimaryBucketResponse response =
                BecomePrimaryBucketMessage.send(members[finalOtherVM], pr, 0, false);
            if (response != null) {
              return Boolean.valueOf(response.waitForResponse());
            } else {
              return Boolean.FALSE;
            }
          }
        })).booleanValue();

    assertTrue(becamePrimary);

    // do a put from all three members to ensure primary is identified
    for (int i = 0; i < localMaxMemory.length; i++) {
      final int vm = i;
      performPuts(vm, regionPath[0], bucketKeys, value);
    }

    // use BucketAdvisor on all three members to assert that otherVM is now the primary
    for (int i = 0; i < localMaxMemory.length; i++) {
      final int vm = i;
      Host.getHost(0).getVM(vm).invoke(new SerializableRunnable() {
        public void run() {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);
          BucketAdvisor advisor = pr.getRegionAdvisor().getBucketAdvisor(0);
          assertEquals(members[finalOtherVM], advisor.getPrimary());
        }
      });
    }
  }

  private static interface OpDuringBucketRemove extends java.io.Serializable {
    public void runit(PartitionedRegion pr, Object key, Object value);
  }

  private void doOpDuringBucketRemove(final OpDuringBucketRemove op) {
    final String[] regionPath = new String[] {getUniqueName() + "_PR_0"};
    final int[] numBuckets = new int[] {1};
    final int[] redundantCopies = new int[] {1};

    // localMaxMemory config to use for three members
    final int[] localMaxMemory = new int[] {100, 100};

    final Integer KEY = Integer.valueOf(69);
    // bucketKeys to use for making one bucket
    final Integer[] bucketKeys = new Integer[] {KEY};

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory[0], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory[1], numBuckets[0],
        redundantCopies[0]);

    final String VALUE = "doOpDuringBucketRemove.VALUE";

    createBuckets(0, regionPath[0], bucketKeys, VALUE);

    // identify the members and their config values
    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    final int finalOtherVM = otherVM;

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);
        Bucket bucket = pr.getRegionAdvisor().getBucket(0);
        assertTrue("Target member is not hosting bucket to remove", bucket.isHosting());
        assertNotNull("Bucket is null on target member", bucket);
        assertNotNull("BucketRegion is null on target member",
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion());
        EntrySnapshot re = (EntrySnapshot) pr.getEntry(KEY);
        assertEquals(true, re.wasInitiallyLocal());
        assertEquals(false, re.isLocal());
        assertEquals(VALUE, re.getValue());
      }
    });
    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        final PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);
        final boolean[] invoked = new boolean[] {false};
        final PartitionedRegionDataStore prds = pr.getDataStore();
        prds.setBucketReadHook(new Runnable() {
          public void run() {
            invoked[0] = true;
            logger.debug("In bucketReadHook");
            assertTrue(prds.removeBucket(0, false));
          }
        });
        try {
          Bucket bucket = pr.getRegionAdvisor().getBucket(0);
          assertTrue("Target member is not hosting bucket to remove", bucket.isHosting());
          assertNotNull("Bucket is null on target member", bucket);
          assertNotNull("BucketRegion is null on target member",
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion());
          assertEquals(false, invoked[0]);
          op.runit(pr, KEY, VALUE);
          assertEquals(true, invoked[0]);
        } finally {
          prds.setBucketReadHook(null);
        }
      }
    });

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

        Bucket bucket = pr.getRegionAdvisor().getBucket(0);
        BucketRegion bucketRegion =
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

        assertFalse("Target member is still hosting removed bucket. Bucket:" + bucket
            + " Advisor state:" + bucket.getBucketAdvisor(), bucket.isHosting());

        assertNull(bucketRegion);
        // assertTrue(bucketRegion == null || bucketRegion.isDestroyed());
        EntrySnapshot re = (EntrySnapshot) pr.getEntry(KEY);
        assertEquals(false, re.wasInitiallyLocal());
        assertEquals(false, re.isLocal());
        assertEquals(VALUE, re.getValue());
      }
    });
  }

  @Test
  public void testRemoveDuringGetEntry() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        Region.Entry re = pr.getEntry(key);
        assertNotNull("region entry should have existed", re);
        assertEquals(false, re.isLocal());
        assertEquals(value, re.getValue());
      }
    });
  }

  @Category(FlakyTest.class) // GEODE-755: thread unsafe test hook (bucketReadHook), remove bucket
                             // fails, possible product bug in rebalancing
  @Test
  public void testRemoveDuringGet() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        assertEquals(value, pr.get(key));
      }
    });
  }

  @Category(FlakyTest.class) // GEODE-673: thread unsafe test hook (bucketReadHook), remove bucket
                             // fails, possible product bug in rebalancing
  @Test
  public void testRemoveDuringContainsKey() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        assertEquals(true, pr.containsKey(key));
      }
    });
  }

  @Test
  public void testRemoveDuringContainsValueForKey() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        assertEquals(true, pr.containsValueForKey(key));
      }
    });
  }

  @Category(FlakyTest.class) // GEODE-1768: thread unsafe test hook (bucketReadHook), remove bucket
                             // fails, possible product bug in rebalancing
  @Test
  public void testRemoveDuringKeySet() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        assertEquals(Collections.singleton(key), pr.keySet());
      }
    });
  }

  @Test
  public void testRemoveDuringValues() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        assertEquals(Collections.singleton(value), pr.values());
      }
    });
  }

  @Test
  public void testRemoveDuringEntrySet() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        Iterator it = pr.entrySet(false).iterator();
        Region.Entry re = (Region.Entry) it.next();
        assertEquals(value, re.getValue());
        assertEquals(key, re.getKey());
        assertEquals(false, it.hasNext());
      }
    });
  }

  @Test
  public void testRemoveDuringQuery() {
    doOpDuringBucketRemove(new OpDuringBucketRemove() {
      public void runit(PartitionedRegion pr, Object key, Object value) {
        try {
          SelectResults sr = pr.query("toString()='doOpDuringBucketRemove.VALUE'"); // fetch
                                                                                    // everything
          assertEquals(1, sr.size());
          assertEquals(value, sr.iterator().next());
        } catch (QueryException ex) {
          Assert.fail("didn't expect a QueryException", ex);
        } catch (QueryInvalidException ex2) {
          Assert.fail("didn't expect QueryInvalidException", ex2);
        }
      }
    });
  }

  @Test
  public void testRemoveBucketMessage() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0"};
    final int[] numBuckets = new int[] {1};
    final int[] redundantCopies = new int[] {1};

    // localMaxMemory config to use for 2 members
    final int[] localMaxMemory = new int[] {100, 100};

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory[0], numBuckets[0],
        redundantCopies[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory[1], numBuckets[0],
        redundantCopies[0]);

    final Integer bucketKey = Integer.valueOf(0);
    final byte[] value = new byte[1]; // 2 MB in size

    createBucket(0, regionPath[0], bucketKey, value);

    // identify the members and their config values
    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    final int finalOtherVM = otherVM;

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);
        Bucket bucket = pr.getRegionAdvisor().getBucket(0);
        assertTrue("Target member is not hosting bucket to remove", bucket.isHosting());
        assertNotNull("Bucket is null on target member", bucket);
        assertNotNull("BucketRegion is null on target member",
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion());
      }
    });

    boolean sentRemoveBucket =
        ((Boolean) Host.getHost(0).getVM(primaryVM).invoke(new SerializableCallable() {
          public Object call() {
            InternalDistributedMember recipient = members[finalOtherVM];

            PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

            RemoveBucketResponse response = RemoveBucketMessage.send(recipient, pr, 0, false);
            if (response != null) {
              response.waitForRepliesUninterruptibly();
              return true;
            } else {
              return Boolean.FALSE;
            }
          }
        })).booleanValue();
    assertTrue("Failed to get reply to RemoveBucketMessage", sentRemoveBucket);

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

        Bucket bucket = pr.getRegionAdvisor().getBucket(0);
        BucketRegion bucketRegion =
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

        assertFalse("Target member is still hosting removed bucket", bucket.isHosting());

        assertNull("BucketRegion is not null on target member", bucketRegion);
      }
    });
  }

  /**
   * Creates a chain of three colocated PRs and then calls removeBucket to make sure that all
   * colocated buckets are removed together.
   */
  @Category(FlakyTest.class) // GEODE-928: RemoveBucketMessage failure?
  @Test
  public void testRemoveColocatedBuckets() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0", getUniqueName() + "-PR-1",
        getUniqueName() + "-PR-2"};

    final int numBuckets = 1;
    final int redundantCopies = 1;
    final int localMaxMemory = 100;

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);

    createRegion(Host.getHost(0).getVM(0), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);

    createRegion(Host.getHost(0).getVM(0), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);
    createRegion(Host.getHost(0).getVM(1), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);

    final Integer bucketKey = Integer.valueOf(0);
    final byte[] value = new byte[1];

    createBucket(0, regionPath[0], bucketKey, value);
    createBucket(0, regionPath[1], bucketKey, value);
    createBucket(0, regionPath[2], bucketKey, value);

    // identify the members and their config values
    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    final int finalOtherVM = otherVM;

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);
          Bucket bucket = pr.getRegionAdvisor().getBucket(0);
          assertTrue("Target member is not hosting bucket to remove for " + regionPath[i],
              bucket.isHosting());
          assertNotNull("Bucket is null on target member for " + regionPath[i], bucket);
          assertNotNull("BucketRegion is null on target member for " + regionPath[i],
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion());
        }
      }
    });

    boolean sentRemoveBucket =
        ((Boolean) Host.getHost(0).getVM(primaryVM).invoke(new SerializableCallable() {
          public Object call() {
            InternalDistributedMember recipient = members[finalOtherVM];

            PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

            RemoveBucketResponse response = RemoveBucketMessage.send(recipient, pr, 0, false);
            if (response != null) {
              response.waitForRepliesUninterruptibly();
              return true;
            } else {
              return Boolean.FALSE;
            }
          }
        })).booleanValue();
    assertTrue("Failed to get reply to RemoveBucketMessage", sentRemoveBucket);

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);

          Bucket bucket = pr.getRegionAdvisor().getBucket(0);
          BucketRegion bucketRegion =
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

          assertFalse("Target member is still hosting removed bucket for " + regionPath[i],
              bucket.isHosting());

          assertNull("BucketRegion is not null on target member for " + regionPath[i],
              bucketRegion);
        }
      }
    });
  }

  /**
   * Creates a bucket on two members. Then brings up a third member and creates an extra redundant
   * copy of the bucket on it.
   */
  @Test
  public void testCreateRedundantBucket() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0"};

    final int numBuckets = 1;
    final int redundantCopies = 1;
    final int localMaxMemory = 100;

    // create the PartitionedRegion on the first two members

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);

    // create the bucket on the first two members

    final Integer bucketKey = Integer.valueOf(0);
    final byte[] value = new byte[1]; // 2 MB in size

    createBucket(0, regionPath[0], bucketKey, value);

    // identify the primaryVM and otherVM

    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    final int finalOtherVM = otherVM;

    // make sure bucket exists on otherVM

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

        Bucket bucket = pr.getRegionAdvisor().getBucket(0);

        assertNotNull("Bucket is null on SRC member", bucket);

        BucketRegion bucketRegion =
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

        assertTrue("SRC member is not hosting bucket", bucket.isHosting());
        assertNotNull("BucketRegion is null on SRC member", bucketRegion);

        int redundancy = bucket.getBucketAdvisor().getBucketRedundancy();
        assertEquals("SRC member reports redundancy " + redundancy, redundantCopies, redundancy);
      }
    });

    // create newVM to create extra redundant bucket on

    final int finalNewVM = 2;
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);

    // create an extra redundant bucket on finalNewVM

    Host.getHost(0).getVM(finalNewVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

        assertEquals(CreateBucketResult.CREATED,
            pr.getDataStore().createRedundantBucket(0, false, new InternalDistributedMember()));

        Bucket bucket = pr.getRegionAdvisor().getBucket(0);

        assertNotNull("Bucket is null on DST member", bucket);

        BucketRegion bucketRegion =
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

        assertTrue("DST member is not hosting bucket", bucket.isHosting());
        assertNotNull("BucketRegion is null on DST member", bucketRegion);
        assertEquals(redundantCopies + 1, bucket.getBucketAdvisor().getBucketRedundancy());
      }
    });
  }

  /**
   * Creates colocated buckets on two members. Then brings up a third member and creates an extra
   * redundant copy of the buckets on it.
   */
  @Test
  public void testCreateRedundantColocatedBuckets() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0", getUniqueName() + "-PR-1",
        getUniqueName() + "-PR-2"};

    final int numBuckets = 1;
    final int redundantCopies = 1;
    final int localMaxMemory = 100;

    // create the PartitionedRegion on the first two members

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);

    createRegion(Host.getHost(0).getVM(0), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);

    createRegion(Host.getHost(0).getVM(0), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);
    createRegion(Host.getHost(0).getVM(1), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);

    // create the bucket on the first two members

    final Integer bucketKey = Integer.valueOf(0);
    final byte[] value = new byte[1];

    createBucket(0, regionPath[0], bucketKey, value);
    createBucket(0, regionPath[1], bucketKey, value);
    createBucket(0, regionPath[2], bucketKey, value);

    // identify the primaryVM and otherVM

    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    final int finalOtherVM = otherVM;

    // make sure colocated buckets exists on otherVM

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);

          Bucket bucket = pr.getRegionAdvisor().getBucket(0);

          assertNotNull("Bucket is null on SRC member for " + regionPath[i], bucket);

          BucketRegion bucketRegion =
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

          assertTrue("SRC member is not hosting bucket for " + regionPath[i], bucket.isHosting());
          assertNotNull("BucketRegion is null on SRC member for " + regionPath[i], bucketRegion);

          int redundancy = bucket.getBucketAdvisor().getBucketRedundancy();
          assertEquals("SRC member reports redundancy " + redundancy + " for " + regionPath[i],
              redundantCopies, redundancy);
        }
      }
    });

    // create newVM to create extra redundant buckets on

    final int finalNewVM = 2;
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);

    // create extra redundant buckets on finalNewVM

    Host.getHost(0).getVM(finalNewVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);

          if (i == 0) {
            // only call createRedundantBucket on leader PR
            pr.getDataStore().createRedundantBucket(0, false, new InternalDistributedMember());
          }

          Bucket bucket = pr.getRegionAdvisor().getBucket(0);

          assertNotNull("Bucket is null on DST member", bucket);

          BucketRegion bucketRegion =
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

          assertTrue("DST member is not hosting bucket", bucket.isHosting());
          assertNotNull("BucketRegion is null on DST member", bucketRegion);
          assertEquals(redundantCopies + 1, bucket.getBucketAdvisor().getBucketRedundancy());
        }
      }
    });
  }

  /**
   * Creates a bucket on two members. Then brings up a third member and moves the non-primary bucket
   * to it.
   */
  @Test
  public void testMoveBucket() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0"};

    final int numBuckets = 1;
    final int redundantCopies = 1;
    final int localMaxMemory = 100;

    // create the PartitionedRegion on the first two members

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);

    // create the bucket on the first two members

    final Integer bucketKey = Integer.valueOf(0);
    final byte[] value = new byte[1]; // 2 MB in size

    createBucket(0, regionPath[0], bucketKey, value);

    // identify the primaryVM and otherVM

    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    final int finalOtherVM = otherVM;

    // make sure bucket exists on otherVM

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

        Bucket bucket = pr.getRegionAdvisor().getBucket(0);

        assertNotNull("Bucket is null on SRC member", bucket);

        BucketRegion bucketRegion =
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

        assertTrue("SRC member is not hosting bucket", bucket.isHosting());
        assertNotNull("BucketRegion is null on SRC member", bucketRegion);

        int redundancy = bucket.getBucketAdvisor().getBucketRedundancy();
        assertEquals("SRC member reports redundancy " + redundancy, redundantCopies, redundancy);
      }
    });

    // create newVM to move bucket to

    final int finalNewVM = 2;
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);

    // initiate moveBucket to move from otherVM to newVM

    boolean movedBucket =
        ((Boolean) Host.getHost(0).getVM(finalNewVM).invoke(new SerializableCallable() {
          public Object call() {
            InternalDistributedMember recipient = members[finalOtherVM];

            PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

            return pr.getDataStore().moveBucket(0, recipient, true);
          }
        })).booleanValue();
    assertTrue("Failed in call to moveBucket", movedBucket);

    // validate that otherVM no longer hosts bucket

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

        Bucket bucket = pr.getRegionAdvisor().getBucket(0);

        assertFalse("SRC member is still hosting moved bucket", bucket.isHosting());

        BucketRegion bucketRegion =
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

        assertNull("BucketRegion is not null on SRC member", bucketRegion);
      }
    });

    // validate that newVM now hosts bucket

    Host.getHost(0).getVM(finalNewVM).invoke(new SerializableRunnable() {
      public void run() {
        PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

        Bucket bucket = pr.getRegionAdvisor().getBucket(0);

        assertNotNull("Bucket is null on DST member", bucket);

        BucketRegion bucketRegion =
            bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

        assertTrue("DST member is not hosting bucket", bucket.isHosting());
        assertNotNull("BucketRegion is null on DST member", bucketRegion);
        assertEquals(redundantCopies, bucket.getBucketAdvisor().getBucketRedundancy());
      }
    });
  }

  /**
   * Creates colocated buckets on two members. Then brings up a third member and moves the
   * non-primary colocated buckets to it.
   */
  @Test
  public void testMoveColocatedBuckets() {
    final String[] regionPath = new String[] {getUniqueName() + "-PR-0", getUniqueName() + "-PR-1",
        getUniqueName() + "-PR-2"};

    final int numBuckets = 1;
    final int redundantCopies = 1;
    final int localMaxMemory = 100;

    // create the PartitionedRegion on the first two members

    createRegion(Host.getHost(0).getVM(0), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);
    createRegion(Host.getHost(0).getVM(1), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);

    createRegion(Host.getHost(0).getVM(0), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);
    createRegion(Host.getHost(0).getVM(1), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);

    createRegion(Host.getHost(0).getVM(0), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);
    createRegion(Host.getHost(0).getVM(1), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);

    // create the bucket on the first two members

    final Integer bucketKey = Integer.valueOf(0);
    final byte[] value = new byte[1];

    createBucket(0, regionPath[0], bucketKey, value);
    createBucket(0, regionPath[1], bucketKey, value);
    createBucket(0, regionPath[2], bucketKey, value);

    // identify the primaryVM and otherVM

    final InternalDistributedMember[] members = new InternalDistributedMember[2];
    final long[] memberSizes = new long[members.length];
    final int[] memberBucketCounts = new int[members.length];
    final int[] memberPrimaryCounts = new int[members.length];

    fillValidationArrays(members, memberSizes, memberBucketCounts, memberPrimaryCounts,
        regionPath[0]);

    int primaryVM = -1;
    int otherVM = -1;
    for (int i = 0; i < memberPrimaryCounts.length; i++) {
      if (memberPrimaryCounts[i] == 0) {
        otherVM = i;
      } else if (memberPrimaryCounts[i] == 1) {
        // found the primary
        primaryVM = i;
      }
    }

    assertTrue(primaryVM > -1);
    assertTrue(otherVM > -1);
    assertTrue(primaryVM != otherVM);

    final int finalOtherVM = otherVM;

    // make sure colocated buckets exists on otherVM

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);

          Bucket bucket = pr.getRegionAdvisor().getBucket(0);

          assertNotNull("Bucket is null on SRC member", bucket);

          BucketRegion bucketRegion =
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

          assertTrue("SRC member is not hosting bucket", bucket.isHosting());
          assertNotNull("BucketRegion is null on SRC member", bucketRegion);

          int redundancy = bucket.getBucketAdvisor().getBucketRedundancy();
          assertEquals("SRC member reports redundancy " + redundancy, redundantCopies, redundancy);
        }
      }
    });

    // create newVM to create extra redundant buckets on

    final int finalNewVM = 2;
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[0], localMaxMemory, numBuckets,
        redundantCopies);
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[1], localMaxMemory, numBuckets,
        redundantCopies, regionPath[0]);
    createRegion(Host.getHost(0).getVM(finalNewVM), regionPath[2], localMaxMemory, numBuckets,
        redundantCopies, regionPath[1]);

    // create extra redundant buckets on finalNewVM

    Host.getHost(0).getVM(finalNewVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);

          if (i == 0) {
            // only call createRedundantBucket on leader PR
            assertEquals(CreateBucketResult.CREATED,
                pr.getDataStore().createRedundantBucket(0, false, new InternalDistributedMember()));
          }

          Bucket bucket = pr.getRegionAdvisor().getBucket(0);

          assertNotNull("Bucket is null on DST member", bucket);

          BucketRegion bucketRegion =
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

          assertTrue("DST member is not hosting bucket", bucket.isHosting());
          assertNotNull("BucketRegion is null on DST member", bucketRegion);
          assertEquals(redundantCopies + 1, bucket.getBucketAdvisor().getBucketRedundancy());
        }
      }
    });

    if (true)
      return;

    // initiate moveBucket to move from otherVM to newVM

    boolean movedBucket =
        ((Boolean) Host.getHost(0).getVM(finalNewVM).invoke(new SerializableCallable() {
          public Object call() {
            InternalDistributedMember recipient = members[finalOtherVM];

            PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[0]);

            return pr.getDataStore().moveBucket(0, recipient, true);
          }
        })).booleanValue();
    assertTrue("Failed in call to moveBucket", movedBucket);

    // validate that otherVM no longer hosts colocated buckets

    Host.getHost(0).getVM(otherVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);

          Bucket bucket = pr.getRegionAdvisor().getBucket(0);

          assertFalse("SRC member is still hosting moved bucket", bucket.isHosting());

          BucketRegion bucketRegion =
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

          assertNull("BucketRegion is not null on SRC member", bucketRegion);
        }
      }
    });

    // validate that newVM now hosts colocated bucket

    Host.getHost(0).getVM(finalNewVM).invoke(new SerializableRunnable() {
      public void run() {
        for (int i = 0; i < regionPath.length; i++) {
          PartitionedRegion pr = (PartitionedRegion) getCache().getRegion(regionPath[i]);

          Bucket bucket = pr.getRegionAdvisor().getBucket(0);

          assertNotNull("Bucket is null on DST member", bucket);

          BucketRegion bucketRegion =
              bucket.getBucketAdvisor().getProxyBucketRegion().getHostedBucketRegion();

          assertTrue("DST member is not hosting bucket", bucket.isHosting());
          assertNotNull("BucketRegion is null on DST member", bucketRegion);
          assertEquals(redundantCopies, bucket.getBucketAdvisor().getBucketRedundancy());
        }
      }
    });
  }
}
