blob: 70967e4f63b26c14eb8da92047cf41eb26c37364 [file] [log] [blame]
/*
* 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.modules.util;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
import org.apache.geode.DataSerializable;
import org.apache.geode.InternalGemFireError;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Declarable;
import org.apache.geode.cache.EvictionAction;
import org.apache.geode.cache.EvictionAttributes;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.cache.partition.PartitionRegionHelper;
import org.apache.geode.distributed.DistributedLockService;
import org.apache.geode.distributed.internal.locks.DistributedMemberLock;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalRegionArguments;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator;
import org.apache.geode.internal.cache.xmlcache.RegionAttributesCreation;
import org.apache.geode.management.internal.security.ResourcePermissions;
import org.apache.geode.security.ResourcePermission;
public class CreateRegionFunction implements Function, Declarable, DataSerializable {
private static final long serialVersionUID = -9210226844302128969L;
public static final String ID = "create-region-function";
private static final boolean DUMP_SESSION_CACHE_XML =
Boolean.getBoolean("gemfiremodules.dumpSessionCacheXml");
static final String REGION_CONFIGURATION_METADATA_REGION =
"__regionConfigurationMetadata";
private final Cache cache;
private final Region<String, RegionConfiguration> regionConfigurationsRegion;
public CreateRegionFunction() {
this.cache = CacheFactory.getAnyInstance();
this.regionConfigurationsRegion = createRegionConfigurationMetadataRegion();
}
@Override
@SuppressWarnings("unchecked")
public void execute(FunctionContext context) {
RegionConfiguration configuration = (RegionConfiguration) context.getArguments();
if (this.cache.getLogger().fineEnabled()) {
this.cache.getLogger().fine("Function " + ID + " received request: " + configuration);
}
// Create or retrieve region
RegionStatus status = createOrRetrieveRegion(configuration);
// Dump XML
if (DUMP_SESSION_CACHE_XML) {
writeCacheXml();
}
// Return status
context.getResultSender().lastResult(status);
}
@Override
public Collection<ResourcePermission> getRequiredPermissions(String regionName) {
return Collections.singletonList(ResourcePermissions.DATA_MANAGE);
}
private RegionStatus createOrRetrieveRegion(RegionConfiguration configuration) {
RegionStatus status;
String regionName = configuration.getRegionName();
if (this.cache.getLogger().fineEnabled()) {
this.cache.getLogger().fine("Function " + ID + " retrieving region named: " + regionName);
}
Region region = this.cache.getRegion(regionName);
if (region == null) {
status = createRegion(configuration);
} else {
status = RegionStatus.VALID;
try {
RegionAttributes existingRegionAttributes = region.getAttributes();
RegionAttributes requestedRegionAttributes =
RegionHelper.getRegionAttributes(this.cache, configuration);
compareRegionAttributes(existingRegionAttributes, requestedRegionAttributes);
} catch (Exception e) {
if (!e.getMessage()
.equals("CacheListeners are not the same")) {
this.cache.getLogger().warning(e);
}
status = RegionStatus.INVALID;
}
}
return status;
}
/**
* If the existing region was using the DEFAULT diskstore but it was not explicitly linked to the
* region using setDiskStore or disk-store-name tags. diskStoreName is set as null. This is
* interpreted by Geode as use the DEFAULT diskstore.
* This link between the existing region and a diskstore may happen because the diskstore is
* named as DEFAULT.
* The user may change the default location of the DEFAULT diskstore but the AppServer always
* requests it be at the default location.
* This comparison with always used to fail and the AppServer could not start up.
* The goal of this method is that if both existing region and requested region are using the
* DEFAULT diskstore, the existing regions take precedence and the requested ones are ignored.
* This is current behavior which can be seen in
* {@link RegionAttributesCreation#sameAs(RegionAttributes)}
* The logic is to intercept the configurations for the regions and only if the both the regions
* have diskStoreName set to null, meaning both should use the DEFAULT diskstore, the diskstore
* names are sent as DEFAULT in the configuration and send to geode-core for comparison for rest
* of the region attributes.
*/
void compareRegionAttributes(RegionAttributes existingRegionAttributes,
RegionAttributes requestedRegionAttributes) {
EvictionAttributes evictionAttributes = existingRegionAttributes.getEvictionAttributes();
RegionAttributesCreation existingRACreation =
new RegionAttributesCreation(existingRegionAttributes, false);
RegionAttributesCreation requestedAttributesCreation =
new RegionAttributesCreation(requestedRegionAttributes, false);
if (existingRegionAttributes.getDataPolicy().withPersistence() || (evictionAttributes != null
&& evictionAttributes.getAction() == EvictionAction.OVERFLOW_TO_DISK)) {
if (requestedRegionAttributes.getDiskStoreName() == null
&& existingRegionAttributes.getDiskStoreName() == null) {
existingRACreation.setDiskStoreName("DEFAULT");
requestedAttributesCreation.setDiskStoreName("DEFAULT");
}
}
existingRACreation.sameAs(requestedAttributesCreation);
}
@Override
public String getId() {
return ID;
}
@Override
public boolean optimizeForWrite() {
return false;
}
@Override
public boolean isHA() {
return true;
}
@Override
public boolean hasResult() {
return true;
}
private RegionStatus createRegion(RegionConfiguration configuration) {
// Get a distributed lock
DistributedMemberLock dml = getDistributedLock();
if (this.cache.getLogger().fineEnabled()) {
this.cache.getLogger().fine(this + ": Attempting to lock " + dml);
}
long start = 0, end;
RegionStatus status;
try {
if (this.cache.getLogger().fineEnabled()) {
start = System.currentTimeMillis();
}
// Obtain a lock on the distributed lock
dml.lockInterruptibly();
if (this.cache.getLogger().fineEnabled()) {
end = System.currentTimeMillis();
this.cache.getLogger()
.fine(this + ": Obtained lock on " + dml + " in " + (end - start) + " ms");
}
// Attempt to get the region again after the lock has been obtained
String regionName = configuration.getRegionName();
Region region = this.cache.getRegion(regionName);
// If it exists now, validate it.
// Else put an entry into the sessionRegionConfigurationsRegion
// while holding the lock. This will create the region in all VMs.
if (region == null) {
this.regionConfigurationsRegion.put(regionName, configuration);
// Retrieve the region after creating it
region = this.cache.getRegion(regionName);
// If the region is null now, it wasn't created for some reason
// (e.g. the region attributes id were invalid)
if (region == null) {
status = RegionStatus.INVALID;
} else {
// Create the PR buckets if necessary)
if (region instanceof PartitionedRegion) {
PartitionedRegion pr = (PartitionedRegion) region;
createBuckets(pr);
}
status = RegionStatus.VALID;
}
} else {
status = RegionStatus.VALID;
try {
RegionHelper.validateRegion(this.cache, configuration, region);
} catch (Exception e) {
if (!e.getMessage()
.equals("CacheListeners are not the same")) {
this.cache.getLogger().warning(e);
}
status = RegionStatus.INVALID;
}
}
} catch (Exception e) {
String builder = this + ": Caught Exception attempting to create region named "
+ configuration.getRegionName() + ":";
this.cache.getLogger().warning(builder, e);
status = RegionStatus.INVALID;
} finally {
// Unlock the distributed lock
try {
dml.unlock();
} catch (Exception ignore) {
}
}
return status;
}
private void createBuckets(PartitionedRegion region) {
PartitionRegionHelper.assignBucketsToPartitions(region);
}
@SuppressWarnings("unchecked")
private Region<String, RegionConfiguration> createRegionConfigurationMetadataRegion() {
// a sessionFactory in hibernate could have been re-started
// so, it is possible that this region exists already
Region<String, RegionConfiguration> region =
this.cache.getRegion(REGION_CONFIGURATION_METADATA_REGION);
if (region != null) {
return region;
}
GemFireCacheImpl gemFireCache = (GemFireCacheImpl) cache;
InternalRegionArguments ira = new InternalRegionArguments().setInternalRegion(true);
RegionAttributesCreation regionAttributesCreation = new RegionAttributesCreation();
regionAttributesCreation.setScope(Scope.DISTRIBUTED_ACK);
regionAttributesCreation.setDataPolicy(DataPolicy.REPLICATE);
regionAttributesCreation.addCacheListener(new RegionConfigurationCacheListener());
try {
return gemFireCache.createVMRegion(REGION_CONFIGURATION_METADATA_REGION,
regionAttributesCreation, ira);
} catch (IOException | ClassNotFoundException e) {
InternalGemFireError assErr = new InternalGemFireError("unexpected exception");
assErr.initCause(e);
throw assErr;
}
}
private void writeCacheXml() {
File file = new File("cache-" + System.currentTimeMillis() + ".xml");
try {
PrintWriter pw = new PrintWriter(new FileWriter(file), true);
CacheXmlGenerator.generate(this.cache, pw);
pw.close();
} catch (IOException ignored) {
}
}
private DistributedMemberLock getDistributedLock() {
String dlsName = this.regionConfigurationsRegion.getName();
DistributedLockService lockService = initializeDistributedLockService(dlsName);
String lockToken = dlsName + "_token";
return new DistributedMemberLock(lockService, lockToken);
}
private DistributedLockService initializeDistributedLockService(String dlsName) {
DistributedLockService lockService = DistributedLockService.getServiceNamed(dlsName);
if (lockService == null) {
lockService = DistributedLockService.create(dlsName, this.cache.getDistributedSystem());
}
return lockService;
}
@Override
public void toData(DataOutput out) {}
@Override
public void fromData(DataInput in) {}
}