| /* |
| * 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; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.logging.log4j.Logger; |
| |
| import org.apache.geode.cache.AttributesFactory; |
| import org.apache.geode.cache.DataPolicy; |
| import org.apache.geode.cache.DuplicatePrimaryPartitionException; |
| import org.apache.geode.cache.EvictionAttributes; |
| import org.apache.geode.cache.FixedPartitionAttributes; |
| import org.apache.geode.cache.PartitionAttributes; |
| import org.apache.geode.cache.RegionAttributes; |
| import org.apache.geode.cache.Scope; |
| import org.apache.geode.internal.Assert; |
| import org.apache.geode.internal.logging.LogService; |
| |
| public class PartitionRegionConfigValidator { |
| |
| private static final Logger logger = LogService.getLogger(); |
| |
| private final PartitionedRegion pr; |
| |
| // Incompatible LRU memory eviction attributes maximum message fragment, for |
| // tests |
| public static final String EVICTION_ATTRIBUTE_MAXIMUM_MEMORY_MESSAGE = |
| " the Eviction Attribute for maximum memory, "; |
| |
| // Incompatible LRU entry eviction attributes maximum message fragment, for |
| // tests |
| public static final String EVICTION_ATTRIBUTE_MAXIMUM_ENTRIES_MESSAGE = |
| " the Eviction Attribute for maximum entries, "; |
| |
| // Incompatible eviction attributes exception message fragment, for tests |
| public static final String EVICTION_ATTRIBUTES_ARE_INCOMPATIBLE_MESSAGE = |
| " is incompatible with other VMs which have EvictionAttributes "; |
| |
| public PartitionRegionConfigValidator(PartitionedRegion pr) { |
| this.pr = pr; |
| } |
| |
| /** |
| * This method validates the PartitionedAttributes that user provided PR Attributes with PR |
| * Attributes set in PR Config obtained from global meta-data allPartitionedRegion region |
| */ |
| void validatePartitionAttrsFromPRConfig(PartitionRegionConfig prconf) { |
| final PartitionAttributes prconfPA = prconf.getPartitionAttrs(); |
| final PartitionAttributes userPA = pr.getAttributes().getPartitionAttributes(); |
| |
| if (userPA.getTotalSize() != prconfPA.getTotalSize()) { |
| throw new IllegalStateException( |
| String.format( |
| "Total size in PartitionAttributes is incompatible with globally set total size. Set the total size to %sMB.", |
| Long.valueOf(prconfPA.getTotalSize()))); |
| } |
| if (userPA.getRedundantCopies() != prconfPA.getRedundantCopies()) { |
| throw new IllegalStateException( |
| String.format("Requested redundancy %s is incompatible with existing redundancy %s", |
| new Object[] {Integer.valueOf(userPA.getRedundantCopies()), |
| Integer.valueOf(prconfPA.getRedundantCopies())})); |
| } |
| |
| if (prconf.isFirstDataStoreCreated() && pr.isDataStore()) { |
| validateDistributedEvictionAttributes(prconf.getEvictionAttributes()); |
| } |
| |
| Scope prconfScope = prconf.getScope(); |
| Scope myScope = pr.getScope(); |
| if (!myScope.equals(prconfScope)) { |
| throw new IllegalStateException( |
| String.format( |
| "Scope in PartitionAttributes is incompatible with already set scope.Set the scope to %s .", |
| prconfScope)); |
| } |
| |
| final int prconfTotalNumBuckets = prconfPA.getTotalNumBuckets(); |
| if (userPA.getTotalNumBuckets() != prconfTotalNumBuckets) { |
| throw new IllegalStateException( |
| String.format( |
| "The total number of buckets found in PartitionAttributes ( %s ) is incompatible with the total number of buckets used by other distributed members. Set the number of buckets to %s", |
| new Object[] {Integer.valueOf(userPA.getTotalNumBuckets()), |
| Integer.valueOf(prconfTotalNumBuckets)})); |
| } |
| validatePartitionListeners(prconf, userPA); |
| validatePartitionResolver(prconf, userPA); |
| validateColocatedWith(prconf, userPA); |
| validateExpirationAttributes(pr.getAttributes(), prconf); |
| } |
| |
| private void validatePartitionListeners(final PartitionRegionConfig prconf, |
| final PartitionAttributes userPA) { |
| |
| ArrayList<String> prconfList = prconf.getPartitionListenerClassNames(); |
| |
| if (userPA.getPartitionListeners() == null && userPA.getPartitionListeners().length == 0 |
| && prconfList != null) { |
| throw new IllegalStateException( |
| String.format( |
| "The PartitionListeners=%s are incompatible with the PartitionListeners=%s used by other distributed members.", |
| new Object[] {null, prconfList})); |
| } |
| if (userPA.getPartitionListeners() != null && prconfList != null) { |
| ArrayList<String> userPRList = new ArrayList<String>(); |
| for (int i = 0; i < userPA.getPartitionListeners().length; i++) { |
| userPRList.add(userPA.getPartitionListeners()[i].getClass().getName()); |
| } |
| |
| if (userPA.getPartitionListeners().length != prconfList.size()) { |
| throw new IllegalStateException( |
| String.format( |
| "The PartitionListeners=%s are incompatible with the PartitionListeners=%s used by other distributed members.", |
| new Object[] {userPRList, prconfList})); |
| } |
| |
| for (String listener : prconfList) { |
| if (!(userPRList.contains(listener))) { |
| throw new IllegalStateException( |
| String.format( |
| "The PartitionListeners=%s are incompatible with the PartitionListeners=%s used by other distributed members.", |
| new Object[] {userPRList, prconfList})); |
| } |
| } |
| } |
| } |
| |
| private void validatePartitionResolver(final PartitionRegionConfig prconf, |
| final PartitionAttributes userPA) { |
| /* |
| * if (userPA.getPartitionResolver() == null && prconf.getPartitionResolverClassName() != null) |
| * { throw new IllegalStateException( |
| * String. |
| * format("The PartitionResolver=%s is incompatible with the PartitionResolver=%s used by other distributed members." |
| * , |
| * new Object[] { "null", prconf.getPartitionResolverClassName() })); } |
| */ |
| if (userPA.getPartitionResolver() != null && prconf.getResolverClassName() != null) { |
| if (!(prconf.getResolverClassName() |
| .equals(userPA.getPartitionResolver().getClass().getName()))) { |
| throw new IllegalStateException( |
| String.format( |
| "The PartitionResolver=%s is incompatible with the PartitionResolver=%s used by other distributed members.", |
| new Object[] {userPA.getPartitionResolver().getClass().getName(), |
| prconf.getResolverClassName()})); |
| } |
| } |
| } |
| |
| private void validateColocatedWith(final PartitionRegionConfig prconf, |
| final PartitionAttributes userPA) { |
| if (userPA.getColocatedWith() == null && prconf.getColocatedWith() != null) { |
| throw new IllegalStateException( |
| String.format( |
| "The colocatedWith=%s found in PartitionAttributes is incompatible with the colocatedWith=%s used by other distributed members.", |
| new Object[] {"null", prconf.getColocatedWith()})); |
| |
| } |
| if (userPA.getColocatedWith() != null && prconf.getColocatedWith() != null) { |
| if (!(prconf.getColocatedWith().equals(userPA.getColocatedWith()))) { |
| throw new IllegalStateException( |
| String.format( |
| "The colocatedWith=%s found in PartitionAttributes is incompatible with the colocatedWith=%s used by other distributed members.", |
| |
| new Object[] {userPA.getColocatedWith(), prconf.getColocatedWith()})); |
| } |
| } |
| } |
| |
| private void validateExpirationAttributes(final RegionAttributes userRA, |
| final PartitionRegionConfig prconf) { |
| if (!userRA.getRegionIdleTimeout().equals(prconf.getRegionIdleTimeout())) { |
| throw new IllegalStateException( |
| String.format( |
| "The %1$s set in RegionAttributes is incompatible with %1$s used by other distributed members.", |
| new Object[] {" region idle timout "})); |
| } |
| if (!userRA.getRegionTimeToLive().equals(prconf.getRegionTimeToLive())) { |
| throw new IllegalStateException( |
| String.format( |
| "The %1$s set in RegionAttributes is incompatible with %1$s used by other distributed members.", |
| new Object[] {" region time to live "})); |
| } |
| if (!userRA.getEntryIdleTimeout().equals(prconf.getEntryIdleTimeout())) { |
| throw new IllegalStateException( |
| String.format( |
| "The %1$s set in RegionAttributes is incompatible with %1$s used by other distributed members.", |
| new Object[] {" entry idle timout "})); |
| } |
| if (!userRA.getEntryTimeToLive().equals(prconf.getEntryTimeToLive())) { |
| throw new IllegalStateException( |
| String.format( |
| "The %1$s set in RegionAttributes is incompatible with %1$s used by other distributed members.", |
| new Object[] {" entry time to live "})); |
| } |
| } |
| |
| /** |
| * The 2nd step of Eviction Attributes validation to ensure that all VMs are reasonably similar to |
| * prevent weird config. issues. |
| * |
| * @param prconfEa the eviction attributes currently used by other VMs |
| * @see AttributesFactory#validateAttributes(RegionAttributes) |
| * @see #validateEvictionAttributesAgainstLocalMaxMemory() |
| */ |
| private void validateDistributedEvictionAttributes(final EvictionAttributes prconfEa) { |
| final EvictionAttributes ea = pr.getAttributes().getEvictionAttributes(); |
| // there is no such thing as null EvictionAttributes, assert that is true |
| Assert.assertTrue(ea != null); |
| Assert.assertTrue(prconfEa != null); |
| |
| // Enforce that all VMs with this PR have the same Eviction Attributes |
| // Even an accessor should do this to stay consistent with all other |
| // accessor validation/enforcement |
| // *and* because an accessor can set the first/global eviction attributes |
| // If this is an accessor with Evicitonttributes, log an info message |
| // indicating that no eviction will |
| // occur in this VM (duh) |
| // Further validation should occur for datastores to ensure consistent |
| // behavior wrt local max memory and |
| // total number of buckets |
| final boolean equivAlgoAndAction = ea.getAlgorithm().equals(prconfEa.getAlgorithm()) |
| && ea.getAction().equals(prconfEa.getAction()); |
| |
| if (!equivAlgoAndAction) { |
| throw new IllegalStateException( |
| "For Partitioned Region " + pr.getFullPath() + " the configured EvictionAttributes " + ea |
| + EVICTION_ATTRIBUTES_ARE_INCOMPATIBLE_MESSAGE + prconfEa); |
| } else { |
| // Same algo, action... |
| // It is ok to have disparate heap or memory sizes since different |
| // VMs may have different heap or memory sizes, particularly if |
| // the action is overflow, but... |
| // It *is* dangerous to locally destroy entries if all VMs don't have the |
| // same maximum |
| // basically the VM with the smallest maximum may cause erroneous misses |
| // to occur. Warn the user, |
| // but allow the configuration. |
| if (ea.getAction().isLocalDestroy()) { |
| // LRUHeap doesn't support maximum, but other eviction algos do |
| if (!ea.getAlgorithm().isLRUHeap() && ea.getMaximum() != prconfEa.getMaximum()) { |
| logger.warn( |
| "For Partitioned Region {} the locally configured EvictionAttributes {} do not match with other EvictionAttributes {} and may cause misses during reads from VMs with smaller maximums.", |
| new Object[] {pr.getFullPath(), ea, prconfEa}); |
| } |
| } |
| } // end Same algo, action... |
| } |
| |
| /** |
| * The 3rd step of EvictionAttributes validation, where mutation is acceptible This should be done |
| * before buckets are created. Validate EvictionAttributes with respect to localMaxMemory |
| * potentially changing the eviction attributes. |
| * |
| * @see AttributesFactory#validateAttributes(RegionAttributes) |
| * @see #validateDistributedEvictionAttributes(EvictionAttributes) |
| */ |
| void validateEvictionAttributesAgainstLocalMaxMemory() { |
| final EvictionAttributes ea = pr.getEvictionAttributes(); |
| if (pr.getLocalMaxMemory() == 0 && !ea.getAction().isNone()) { |
| // This is an accessor which won't ever do eviction, say so |
| logger.info( |
| "EvictionAttributes {} will have no effect for Partitioned Region {} on this VM because localMaxMemory is {}.", |
| new Object[] {ea, pr.getFullPath(), Integer.valueOf(pr.localMaxMemory)}); |
| } |
| } |
| |
| /** |
| * validates the persistence for datastores should match between members |
| */ |
| void validatePersistentMatchBetweenDataStores(PartitionRegionConfig prconf) { |
| final boolean isPersistent = |
| pr.getAttributes().getDataPolicy() == DataPolicy.PERSISTENT_PARTITION; |
| if (pr.getLocalMaxMemory() == 0 || prconf == null) { |
| return; |
| } |
| Set<Node> nodes = prconf.getNodes(); |
| Iterator itor = nodes.iterator(); |
| while (itor.hasNext()) { |
| Node n = (Node) itor.next(); |
| if (n.getPRType() != Node.ACCESSOR_DATASTORE) { |
| continue; |
| } else { |
| if (n.isPersistent() != (pr.getAttributes() |
| .getDataPolicy() == DataPolicy.PERSISTENT_PARTITION)) { |
| throw new IllegalStateException( |
| "DataPolicy for Datastore members should all be persistent or not."); |
| } |
| } |
| } |
| } |
| |
| void validateColocation() { |
| final PartitionAttributesImpl userPA = |
| (PartitionAttributesImpl) pr.getAttributes().getPartitionAttributes(); |
| |
| userPA.validateColocation(pr.getCache()); // do this here to fix bug 47197 |
| |
| PartitionedRegion colocatedPR = ColocationHelper.getColocatedRegion(pr); |
| if (colocatedPR != null) { |
| if (colocatedPR.getPartitionAttributes().getTotalNumBuckets() != userPA |
| .getTotalNumBuckets()) { |
| throw new IllegalStateException( |
| "Colocated regions should have same number of total-num-buckets"); |
| } |
| if (colocatedPR.getPartitionAttributes().getRedundantCopies() != userPA |
| .getRedundantCopies()) { |
| throw new IllegalStateException( |
| "Colocated regions should have same number of redundant-copies"); |
| } |
| if ((colocatedPR.getPartitionAttributes().getLocalMaxMemory() == 0) |
| && (userPA.getLocalMaxMemory() != 0)) { |
| throw new IllegalStateException("Colocated regions should have accessors at the same node"); |
| } |
| if ((colocatedPR.getLocalMaxMemory() != 0) && (userPA.getLocalMaxMemory() == 0)) { |
| throw new IllegalStateException("Colocated regions should have accessors at the same node"); |
| } |
| if (!pr.isShadowPR()) { |
| if (pr.getAttributes().getDataPolicy().withPersistence()) { |
| if (!colocatedPR.getDataPolicy().withPersistence()) { |
| throw new IllegalStateException( |
| "Cannot colocate a persistent region with a non persistent region"); |
| } |
| } |
| } |
| } |
| } |
| |
| public void validateCacheLoaderWriterBetweenDataStores(PartitionRegionConfig prconf) { |
| if (pr.getLocalMaxMemory() == 0 || prconf == null) { |
| return; |
| } |
| Set<Node> nodes = prconf.getNodes(); |
| Iterator itor = nodes.iterator(); |
| while (itor.hasNext()) { |
| Node n = (Node) itor.next(); |
| if (n.getPRType() != Node.ACCESSOR_DATASTORE) { |
| continue; |
| } else { |
| if (n.isCacheLoaderAttached() && pr.getAttributes().getCacheLoader() == null) { |
| throw new IllegalStateException( |
| String.format( |
| "Incompatible CacheLoader. CacheLoader is not null in partitionedRegion %s on another datastore.", |
| new Object[] {this.pr.getName()})); |
| } |
| if (!n.isCacheLoaderAttached() && pr.getAttributes().getCacheLoader() != null) { |
| throw new IllegalStateException( |
| String.format( |
| "Incompatible CacheLoader. CacheLoader is null in partitionedRegion %s on another datastore.", |
| new Object[] {this.pr.getName()})); |
| } |
| if (n.isCacheWriterAttached() && pr.getAttributes().getCacheWriter() == null) { |
| throw new IllegalStateException( |
| String.format( |
| "Incompatible CacheWriter. CacheWriter is not null in partitionedRegion %s on another datastore.", |
| new Object[] {this.pr.getName()})); |
| } |
| if (!n.isCacheWriterAttached() && pr.getAttributes().getCacheWriter() != null) { |
| throw new IllegalStateException( |
| String.format( |
| "Incompatible CacheWriter. CacheWriter is null in partitionedRegion %s on another datastore.", |
| new Object[] {this.pr.getName()})); |
| } |
| } |
| } |
| } |
| |
| void validateFixedPartitionAttributes() { |
| if (this.pr.getFixedPartitionAttributesImpl() != null) { |
| validatePrimaryFixedPartitionAttributes(); |
| validateFixedPartitionAttributesAgainstRedundantCopies(); |
| validateFixedPartitionAttributesAgainstTotalNumberBuckets(); |
| } |
| } |
| |
| /** |
| * validate that for all partitions defined across all datastores, sum of num-buckets is not more |
| * than total-num-buckets defined |
| */ |
| private void validateFixedPartitionAttributesAgainstTotalNumberBuckets() { |
| for (FixedPartitionAttributesImpl fpa : this.pr.getFixedPartitionAttributesImpl()) { |
| int numBuckets = 0; |
| |
| Set<FixedPartitionAttributesImpl> allFPAs = new HashSet<FixedPartitionAttributesImpl>( |
| this.pr.getRegionAdvisor().adviseAllFixedPartitionAttributes()); |
| allFPAs.add(fpa); |
| |
| for (FixedPartitionAttributes samefpa : allFPAs) { |
| numBuckets = numBuckets + samefpa.getNumBuckets(); |
| } |
| if (numBuckets > this.pr.getTotalNumberOfBuckets()) { |
| throw new IllegalStateException( |
| String.format( |
| "For region %s,sum of num-buckets %s for different primary partitions should not be greater than total-num-buckets %s.", |
| this.pr.getName(), numBuckets, this.pr.getTotalNumberOfBuckets())); |
| } |
| } |
| } |
| |
| /** |
| * Validate that for the given partition, number if secondaries are never exceed redundant copies |
| * defined Validate that the num-buckets defined for a partition are same across all datastores |
| */ |
| private void validateFixedPartitionAttributesAgainstRedundantCopies() { |
| for (FixedPartitionAttributesImpl fpa : this.pr.getFixedPartitionAttributesImpl()) { |
| List<FixedPartitionAttributesImpl> allSameFPAs = |
| this.pr.getRegionAdvisor().adviseSameFPAs(fpa); |
| allSameFPAs.add(fpa); |
| |
| if (!allSameFPAs.isEmpty()) { |
| int numSecondaries = 0; |
| for (FixedPartitionAttributes otherfpa : allSameFPAs) { |
| if (fpa.getNumBuckets() != otherfpa.getNumBuckets()) { |
| throw new IllegalStateException( |
| String.format( |
| "For region %s,for partition %s, num-buckets are not same (%s, %s)across nodes.", |
| this.pr.getName(), fpa.getPartitionName(), |
| fpa.getNumBuckets(), otherfpa.getNumBuckets())); |
| } |
| |
| if (!otherfpa.isPrimary()) { |
| if (++numSecondaries > (this.pr.getRedundantCopies())) { |
| throw new IllegalStateException( |
| String.format( |
| "For region %s, number of secondary partitions %s of a partition %s should never exceed number of redundant copies %s.", |
| this.pr.getName(), numSecondaries, |
| fpa.getPartitionName(), this.pr.getRedundantCopies())); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Validate that same partition is not defined as primary on more that one datastore |
| */ |
| private void validatePrimaryFixedPartitionAttributes() { |
| List<FixedPartitionAttributesImpl> remotePrimaryFPAs = |
| this.pr.getRegionAdvisor().adviseRemotePrimaryFPAs(); |
| |
| for (FixedPartitionAttributes fpa : this.pr.getFixedPartitionAttributesImpl()) { |
| if (fpa.isPrimary() && remotePrimaryFPAs.contains(fpa)) { |
| throw new DuplicatePrimaryPartitionException( |
| String.format( |
| "For region %s, same partition name %s can not be defined as primary on more than one node.", |
| this.pr.getName(), fpa.getPartitionName())); |
| } |
| } |
| } |
| |
| public void validateFixedPABetweenDataStores(PartitionRegionConfig prconf) { |
| boolean isDataStore = this.pr.localMaxMemory > 0; |
| boolean isFixedPR = this.pr.fixedPAttrs != null; |
| |
| Set<Node> nodes = prconf.getNodes(); |
| Iterator<Node> itr = nodes.iterator(); |
| while (itr.hasNext()) { |
| Node n = itr.next(); |
| if (isFixedPR) { |
| if (n.getPRType() == Node.DATASTORE || n.getPRType() == Node.ACCESSOR_DATASTORE) { |
| // throw exception |
| Object[] prms = new Object[] {pr.getName()}; |
| throw new IllegalStateException( |
| String.format( |
| "Region %s uses fixed partitioning but at least one datastore node (localMaxMemory > 0) has no fixed partitions. Please make sure that each datastore creating this region is configured with at least one fixed partition.", |
| prms)); |
| } |
| } else { |
| if (isDataStore) { |
| if (n.getPRType() == Node.FIXED_PR_ACCESSOR || n.getPRType() == Node.FIXED_PR_DATASTORE) { |
| // throw Exception |
| Object[] prms = new Object[] {pr.getName()}; |
| throw new IllegalStateException( |
| String.format( |
| "Region %s uses fixed partitioning but at least one datastore node (localMaxMemory > 0) has no fixed partitions. Please make sure that each datastore creating this region is configured with at least one fixed partition.", |
| prms)); |
| } |
| } |
| } |
| } |
| } |
| } |