/*
* 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.config.QuorumListenerConfig;
import com.hazelcast.core.Member;
import com.hazelcast.core.MembershipEvent;
import com.hazelcast.internal.cluster.impl.MemberSelectingCollection;
import com.hazelcast.nio.ClassLoaderUtil;
import com.hazelcast.quorum.Quorum;
import com.hazelcast.quorum.QuorumEvent;
import com.hazelcast.quorum.QuorumException;
import com.hazelcast.quorum.QuorumListener;
import com.hazelcast.quorum.QuorumService;
import com.hazelcast.spi.EventPublishingService;
import com.hazelcast.spi.EventService;
import com.hazelcast.spi.MemberAttributeServiceEvent;
import com.hazelcast.spi.MembershipAwareService;
import com.hazelcast.spi.MembershipServiceEvent;
import com.hazelcast.spi.NamedOperation;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.QuorumAwareService;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.util.ExceptionUtil;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static com.hazelcast.cluster.memberselector.MemberSelectors.DATA_MEMBER_SELECTOR;
import static com.hazelcast.util.Preconditions.checkNotNull;
/**
* Service containing logic for cluster quorum.
*/
public class QuorumServiceImpl implements EventPublishingService<QuorumEvent, QuorumListener>, MembershipAwareService,
QuorumService {
public static final String SERVICE_NAME = "hz:impl:quorumService";
private final NodeEngineImpl nodeEngine;
private final EventService eventService;
private boolean inactive;
private final Map<String, QuorumImpl> quorums = new HashMap<String, QuorumImpl>();
public QuorumServiceImpl(NodeEngineImpl nodeEngine) {
this.nodeEngine = nodeEngine;
this.eventService = nodeEngine.getEventService();
initializeQuorums();
this.inactive = quorums.isEmpty();
}
public void start() {
initializeListeners();
}
private void initializeQuorums() {
for (QuorumConfig quorumConfig : nodeEngine.getConfig().getQuorumConfigs().values()) {
QuorumImpl quorum = new QuorumImpl(quorumConfig, nodeEngine);
quorums.put(quorumConfig.getName(), quorum);
}
}
private void initializeListeners() {
for (Map.Entry<String, QuorumConfig> configEntry : nodeEngine.getConfig().getQuorumConfigs().entrySet()) {
QuorumConfig config = configEntry.getValue();
String instanceName = configEntry.getKey();
for (QuorumListenerConfig listenerConfig : config.getListenerConfigs()) {
initializeListenerInternal(instanceName, listenerConfig);
}
}
}
private void initializeListenerInternal(String instanceName, QuorumListenerConfig listenerConfig) {
QuorumListener listener = null;
if (listenerConfig.getImplementation() != null) {
listener = listenerConfig.getImplementation();
} else if (listenerConfig.getClassName() != null) {
try {
listener = ClassLoaderUtil
.newInstance(nodeEngine.getConfigClassLoader(), listenerConfig.getClassName());
} catch (Exception e) {
throw ExceptionUtil.rethrow(e);
}
}
if (listener != null) {
addQuorumListener(instanceName, listener);
}
}
public void addQuorumListener(String name, QuorumListener listener) {
eventService.registerLocalListener(SERVICE_NAME, name, listener);
}
/**
* Ensures that the quorum for the given operation is present. This requires that there is a quorum configured under the
* name which is returned by the operation service.
* This in turn requires that the operation is named and that the operation service is a {@link QuorumAwareService}.
*
* @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
*/
public void ensureQuorumPresent(Operation op) {
if (inactive) {
return;
}
QuorumImpl quorum = findQuorum(op);
if (quorum == null) {
return;
}
quorum.ensureQuorumPresent(op);
}
/**
* Returns a {@link QuorumImpl} for the given operation. The operation should be named and the operation service should
* be a {@link QuorumAwareService}. This service will then return the quorum configured under the name returned by the
* operation service.
*
* @param op the operation for which the Quorum should be returned
* @return the quorum
*/
private QuorumImpl findQuorum(Operation op) {
if (!isNamedOperation(op) || !isQuorumAware(op)) {
return null;
}
QuorumAwareService service = op.getService();
String name = ((NamedOperation) op).getName();
String quorumName = service.getQuorumName(name);
if (quorumName == null) {
return null;
}
return quorums.get(quorumName);
}
private boolean isQuorumAware(Operation op) {
return op.getService() instanceof QuorumAwareService;
}
private boolean isNamedOperation(Operation op) {
return op instanceof NamedOperation;
}
@Override
public void dispatchEvent(QuorumEvent event, QuorumListener listener) {
listener.onChange(event);
}
@Override
public void memberAdded(MembershipServiceEvent event) {
updateQuorums(event);
}
@Override
public void memberRemoved(MembershipServiceEvent event) {
updateQuorums(event);
}
@Override
public void memberAttributeChanged(MemberAttributeServiceEvent event) {
// nop
// MemberAttributeServiceEvent does NOT contain set of members
// They cannot change quorum state
}
/**
* Updates the quorum presences if there was a change in data members that own partitions (ignores changes in lite members).
*
* @param event the membership change event
*/
private void updateQuorums(MembershipEvent event) {
final Collection<Member> members = new MemberSelectingCollection<Member>(event.getMembers(), DATA_MEMBER_SELECTOR);
for (QuorumImpl quorum : quorums.values()) {
quorum.update(members);
}
}
@Override
public Quorum getQuorum(String quorumName) {
checkNotNull(quorumName, "quorumName cannot be null!");
Quorum quorum = quorums.get(quorumName);
if (quorum == null) {
throw new IllegalArgumentException("No quorum configuration named [ " + quorumName + " ] is found!");
}
return quorum;
}
}