/*
* Copyright Terracotta, Inc.
*
* Licensed 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.ehcache.clustered.server.store;
import org.ehcache.clustered.common.Consistency;
import org.ehcache.clustered.common.internal.ServerStoreConfiguration;
import org.ehcache.clustered.common.internal.exceptions.ClusterException;
import org.ehcache.clustered.common.internal.exceptions.LifecycleException;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityMessage;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse;
import org.ehcache.clustered.common.internal.messages.EhcacheMessageType;
import org.ehcache.clustered.common.internal.messages.EhcacheOperationMessage;
import org.ehcache.clustered.common.internal.messages.ServerStoreOpMessage;
import org.ehcache.clustered.common.internal.messages.StateRepositoryOpMessage;
import org.ehcache.clustered.common.internal.store.ClusterTierEntityConfiguration;
import org.ehcache.clustered.server.KeySegmentMapper;
import org.ehcache.clustered.server.ServerSideServerStore;
import org.ehcache.clustered.server.internal.messages.EhcacheDataSyncMessage;
import org.ehcache.clustered.server.internal.messages.EhcacheStateRepoSyncMessage;
import org.ehcache.clustered.server.internal.messages.EhcacheSyncMessage;
import org.ehcache.clustered.server.internal.messages.PassiveReplicationMessage;
import org.ehcache.clustered.server.internal.messages.PassiveReplicationMessage.ChainReplicationMessage;
import org.ehcache.clustered.server.internal.messages.PassiveReplicationMessage.InvalidationCompleteMessage;
import org.ehcache.clustered.server.management.ClusterTierManagement;
import org.ehcache.clustered.server.state.ClientMessageTracker;
import org.ehcache.clustered.server.state.EhcacheStateService;
import org.ehcache.clustered.server.state.config.EhcacheStoreStateServiceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.entity.ConfigurationException;
import org.terracotta.entity.PassiveServerEntity;
import org.terracotta.entity.ServiceRegistry;
import java.util.concurrent.TimeoutException;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.isPassiveReplicationMessage;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.isStateRepoOperationMessage;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.isStoreOperationMessage;
/**
* ClusterTierPassiveEntity
*/
public class ClusterTierPassiveEntity implements PassiveServerEntity<EhcacheEntityMessage, EhcacheEntityResponse> {
private static final Logger LOGGER = LoggerFactory.getLogger(ClusterTierPassiveEntity.class);
private final EhcacheStateService stateService;
private final String storeIdentifier;
private final ClusterTierManagement management;
private final ServerStoreConfiguration configuration;
public ClusterTierPassiveEntity(ServiceRegistry registry, ClusterTierEntityConfiguration config, KeySegmentMapper defaultMapper) throws ConfigurationException {
if (config == null) {
throw new ConfigurationException("ClusterTierManagerConfiguration cannot be null");
}
storeIdentifier = config.getStoreIdentifier();
configuration = config.getConfiguration();
stateService = registry.getService(new EhcacheStoreStateServiceConfig(config.getManagerIdentifier(), defaultMapper));
if (stateService == null) {
throw new AssertionError("Server failed to retrieve EhcacheStateService.");
}
management = new ClusterTierManagement(registry, stateService, false, storeIdentifier, config.getManagerIdentifier());
}
protected EhcacheStateService getStateService() {
return stateService;
}
protected String getStoreIdentifier() {
return storeIdentifier;
}
@Override
public void createNew() throws ConfigurationException {
stateService.createStore(storeIdentifier, configuration, false);
management.init();
}
private boolean isEventual() {
return configuration.getConsistency() == Consistency.EVENTUAL;
}
@Override
public void invoke(EhcacheEntityMessage message) {
try {
if (message instanceof EhcacheOperationMessage) {
EhcacheOperationMessage operationMessage = (EhcacheOperationMessage) message;
EhcacheMessageType messageType = operationMessage.getMessageType();
if (isStoreOperationMessage(messageType)) {
try {
invokeServerStoreOperation((ServerStoreOpMessage)message);
} catch (ClusterException e) {
// Store operation should not be critical enough to fail a passive
LOGGER.error("Unexpected exception raised during operation: " + message, e);
}
} else if (isStateRepoOperationMessage(messageType)) {
try {
stateService.getStateRepositoryManager().invoke((StateRepositoryOpMessage)message);
applyMessage(operationMessage);
} catch (ClusterException e) {
// State repository operations should not be critical enough to fail a passive
LOGGER.error("Unexpected exception raised during operation: " + message, e);
}
} else if (isPassiveReplicationMessage(messageType)) {
try {
invokeRetirementMessages((PassiveReplicationMessage)message);
} catch (ClusterException e) {
LOGGER.error("Unexpected exception raised during operation: " + message, e);
}
} else {
throw new AssertionError("Unsupported EhcacheOperationMessage: " + operationMessage.getMessageType());
}
} else if (message instanceof EhcacheSyncMessage) {
invokeSyncOperation((EhcacheSyncMessage) message);
} else {
throw new AssertionError("Unsupported EhcacheEntityMessage: " + message.getClass());
}
} catch (ClusterException e) {
// Reaching here means a lifecycle or sync operation failed
throw new IllegalStateException("A lifecycle or sync operation failed", e);
}
}
private void invokeSyncOperation(EhcacheSyncMessage message) throws ClusterException {
switch (message.getMessageType()) {
case DATA:
EhcacheDataSyncMessage dataSyncMessage = (EhcacheDataSyncMessage) message;
ServerSideServerStore store = stateService.getStore(storeIdentifier);
dataSyncMessage.getChainMap().entrySet().forEach(entry -> {
store.put(entry.getKey(), entry.getValue());
});
break;
case STATE_REPO:
EhcacheStateRepoSyncMessage stateRepoSyncMessage = (EhcacheStateRepoSyncMessage) message;
stateService.getStateRepositoryManager().processSyncMessage(stateRepoSyncMessage);
break;
default:
throw new AssertionError("Unsupported Sync operation " + message.getMessageType());
}
}
private void invokeRetirementMessages(PassiveReplicationMessage message) throws ClusterException {
switch (message.getMessageType()) {
case CHAIN_REPLICATION_OP:
LOGGER.debug("Chain Replication message for msgId {} & client Id {}", message.getId(), message.getClientId());
ChainReplicationMessage retirementMessage = (ChainReplicationMessage) message;
ServerSideServerStore cacheStore = stateService.getStore(storeIdentifier);
if (cacheStore == null) {
// An operation on a non-existent store should never get out of the client
throw new LifecycleException("cluster tier does not exist : '" + storeIdentifier + "'");
}
applyMessage(message);
if (isEventual()) {
stateService.getInvalidationTracker(storeIdentifier).trackHashInvalidation(retirementMessage.getKey());
}
cacheStore.put(retirementMessage.getKey(), retirementMessage.getChain());
break;
case INVALIDATION_COMPLETE:
if (isEventual()) {
InvalidationCompleteMessage invalidationCompleteMessage = (InvalidationCompleteMessage) message;
stateService.getInvalidationTracker(storeIdentifier).untrackHashInvalidation(invalidationCompleteMessage.getKey());
}
break;
case CLEAR_INVALIDATION_COMPLETE:
if (isEventual()) {
stateService.getInvalidationTracker(storeIdentifier).setClearInProgress(false);
}
break;
case CLIENT_ID_TRACK_OP:
stateService.getClientMessageTracker(storeIdentifier).remove(message.getClientId());
break;
default:
throw new AssertionError("Unsupported Retirement Message : " + message);
}
}
private void invokeServerStoreOperation(ServerStoreOpMessage message) throws ClusterException {
ServerSideServerStore cacheStore = stateService.getStore(storeIdentifier);
if (cacheStore == null) {
// An operation on a non-existent store should never get out of the client
throw new LifecycleException("cluster tier does not exist : '" + storeIdentifier + "'");
}
switch (message.getMessageType()) {
case REPLACE: {
ServerStoreOpMessage.ReplaceAtHeadMessage replaceAtHeadMessage = (ServerStoreOpMessage.ReplaceAtHeadMessage)message;
cacheStore.replaceAtHead(replaceAtHeadMessage.getKey(), replaceAtHeadMessage.getExpect(), replaceAtHeadMessage.getUpdate());
break;
}
case CLEAR: {
LOGGER.info("Clearing cluster tier {}", storeIdentifier);
try {
cacheStore.clear();
} catch (TimeoutException e) {
throw new AssertionError("Server side store is not expected to throw timeout exception");
}
if (isEventual()) {
stateService.getInvalidationTracker(storeIdentifier).setClearInProgress(true);
}
break;
}
default:
throw new AssertionError("Unsupported ServerStore operation : " + message.getMessageType());
}
}
private void applyMessage(EhcacheOperationMessage message) {
ClientMessageTracker clientMessageTracker = stateService.getClientMessageTracker(storeIdentifier);
clientMessageTracker.applied(message.getId(), message.getClientId());
}
@Override
public void startSyncEntity() {
LOGGER.info("Sync started.");
}
@Override
public void endSyncEntity() {
LOGGER.info("Sync completed.");
stateService.getClientMessageTracker(storeIdentifier).notifySyncCompleted();
}
@Override
public void startSyncConcurrencyKey(int concurrencyKey) {
LOGGER.info("Sync started for concurrency key {}.", concurrencyKey);
}
@Override
public void endSyncConcurrencyKey(int concurrencyKey) {
LOGGER.info("Sync complete for concurrency key {}.", concurrencyKey);
}
@Override
public void destroy() {
LOGGER.info("Destroying cluster tier '{}'", storeIdentifier);
try {
stateService.destroyServerStore(storeIdentifier);
} catch (ClusterException e) {
throw new AssertionError(e);
}
}
}