/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.quorum.impl;
import com.hazelcast.config.QuorumConfig;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.HazelcastInstanceAware;
import com.hazelcast.core.Member;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.ClassLoaderUtil;
import com.hazelcast.quorum.Quorum;
import com.hazelcast.quorum.QuorumEvent;
import com.hazelcast.quorum.QuorumException;
import com.hazelcast.quorum.QuorumFunction;
import com.hazelcast.quorum.QuorumService;
import com.hazelcast.quorum.QuorumType;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.ReadonlyOperation;
import com.hazelcast.spi.impl.MutatingOperation;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.impl.eventservice.InternalEventService;
import com.hazelcast.util.ExceptionUtil;
import java.util.Collection;
import static com.hazelcast.cluster.memberselector.MemberSelectors.DATA_MEMBER_SELECTOR;
/**
* {@link QuorumImpl} can be used to notify quorum service for a particular quorum result that originated externally.
*/
public class QuorumImpl implements Quorum {
private enum QuorumState {
INITIAL,
PRESENT,
ABSENT
}
private final NodeEngineImpl nodeEngine;
private final String quorumName;
private final int size;
private final QuorumConfig config;
private final InternalEventService eventService;
private QuorumFunction quorumFunction;
// we are updating the quorum state within the single thread of membership event executor
private volatile QuorumState quorumState = QuorumState.INITIAL;
public QuorumImpl(QuorumConfig config, NodeEngineImpl nodeEngine) {
this.nodeEngine = nodeEngine;
this.eventService = nodeEngine.getEventService();
this.config = config;
this.quorumName = config.getName();
this.size = config.getSize();
initializeQuorumFunction(nodeEngine.getHazelcastInstance());
}
/**
* Determines if the quorum is present for the given member collection, caches the result and publishes an event under
* the {@link #quorumName} topic if there was a change in presence.
*
* @param members the members for which the presence is determined
*/
void update(Collection<Member> members) {
QuorumState previousQuorumState = this.quorumState;
QuorumState newQuorumState = QuorumState.ABSENT;
try {
boolean present = quorumFunction.apply(members);
newQuorumState = present ? QuorumState.PRESENT : QuorumState.ABSENT;
} catch (Exception e) {
ILogger logger = nodeEngine.getLogger(QuorumService.class);
logger.severe("Quorum function of quorum: " + quorumName + " failed! Quorum status is set to "
+ newQuorumState, e);
}
this.quorumState = newQuorumState;
if (previousQuorumState != newQuorumState) {
createAndPublishEvent(members, newQuorumState == QuorumState.PRESENT);
}
}
public String getName() {
return quorumName;
}
public int getSize() {
return size;
}
public QuorumConfig getConfig() {
return config;
}
@Override
public boolean isPresent() {
return quorumState == QuorumState.PRESENT;
}
/**
* Returns if quorum is needed for this operation. This is determined by the {@link QuorumConfig#type} and by the type
* of the operation - {@link ReadonlyOperation} or {@link MutatingOperation}.
*
* @param op the operation which is to be executed
* @return if this quorum should be consulted for this operation
* @throws IllegalArgumentException if the quorum configuration type is not handled
*/
private boolean isQuorumNeeded(Operation op) {
QuorumType type = config.getType();
switch (type) {
case WRITE:
return isWriteOperation(op);
case READ:
return isReadOperation(op);
case READ_WRITE:
return isReadOperation(op) || isWriteOperation(op);
default:
throw new IllegalStateException("Unhandled quorum type: " + type);
}
}
private static boolean isReadOperation(Operation op) {
return op instanceof ReadonlyOperation;
}
private static boolean isWriteOperation(Operation op) {
return op instanceof MutatingOperation;
}
/**
* Ensures that the quorum is present for the given operation. First checks if the quorum type defined by the configuration
* covers this operation and checks if the quorum is present. Dispatches an event under the {@link #quorumName} topic
* if membership changed after determining the quorum presence.
*
* @param op the operation for which the quorum should be present
* @throws QuorumException if the operation requires a quorum and the quorum is not present
*/
void ensureQuorumPresent(Operation op) {
if (!isQuorumNeeded(op)) {
return;
}
if (!isPresent()) {
throw newQuorumException();
}
}
private QuorumException newQuorumException() {
if (size == 0) {
throw new QuorumException("Cluster quorum failed");
}
Collection<Member> memberList = nodeEngine.getClusterService().getMembers(DATA_MEMBER_SELECTOR);
throw new QuorumException("Cluster quorum failed, quorum minimum size: "
+ size + ", current size: " + memberList.size());
}
private void createAndPublishEvent(Collection<Member> memberList, boolean presence) {
QuorumEvent quorumEvent = new QuorumEvent(nodeEngine.getThisAddress(), size, memberList, presence);
eventService.publishEvent(QuorumServiceImpl.SERVICE_NAME, quorumName, quorumEvent, quorumEvent.hashCode());
}
private void initializeQuorumFunction(HazelcastInstance hazelcastInstance) {
if (config.getQuorumFunctionImplementation() != null) {
quorumFunction = config.getQuorumFunctionImplementation();
} else if (config.getQuorumFunctionClassName() != null) {
try {
quorumFunction = ClassLoaderUtil
.newInstance(nodeEngine.getConfigClassLoader(), config.getQuorumFunctionClassName());
} catch (Exception e) {
throw ExceptionUtil.rethrow(e);
}
}
if (quorumFunction == null) {
quorumFunction = new MemberCountQuorumFunction();
}
if (quorumFunction instanceof HazelcastInstanceAware) {
((HazelcastInstanceAware) quorumFunction).setHazelcastInstance(hazelcastInstance);
}
}
private class MemberCountQuorumFunction implements QuorumFunction {
@Override
public boolean apply(Collection<Member> members) {
return members.size() >= size;
}
}
@Override
public String toString() {
return "QuorumImpl{"
+ "quorumName='" + quorumName + '\''
+ ", isPresent=" + isPresent()
+ ", size=" + size
+ ", config=" + config
+ ", quorumFunction=" + quorumFunction
+ '}';
}
}