All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.splitbrainprotection.impl.SplitBrainProtectionImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, 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.splitbrainprotection.impl;

import com.hazelcast.config.SplitBrainProtectionConfig;
import com.hazelcast.core.ManagedContext;
import com.hazelcast.cluster.Member;
import com.hazelcast.cluster.MembershipEvent;
import com.hazelcast.cluster.MembershipListener;
import com.hazelcast.logging.ILogger;
import com.hazelcast.splitbrainprotection.HeartbeatAware;
import com.hazelcast.splitbrainprotection.PingAware;
import com.hazelcast.splitbrainprotection.SplitBrainProtection;
import com.hazelcast.splitbrainprotection.SplitBrainProtectionEvent;
import com.hazelcast.splitbrainprotection.SplitBrainProtectionException;
import com.hazelcast.splitbrainprotection.SplitBrainProtectionFunction;
import com.hazelcast.splitbrainprotection.SplitBrainProtectionService;
import com.hazelcast.splitbrainprotection.SplitBrainProtectionOn;
import com.hazelcast.spi.impl.operationservice.Operation;
import com.hazelcast.spi.impl.operationservice.ReadonlyOperation;
import com.hazelcast.spi.impl.operationservice.MutatingOperation;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.impl.eventservice.EventService;
import com.hazelcast.spi.impl.operationservice.SplitBrainProtectionCheckAwareOperation;

import java.util.Collection;

import static com.hazelcast.internal.nio.ClassLoaderUtil.newInstance;
import static com.hazelcast.internal.util.ExceptionUtil.rethrow;

/**
 * {@link SplitBrainProtectionImpl} can be used to notify split brain protection service for a particular split brain protection
 * result that originated externally.
 */
public class SplitBrainProtectionImpl implements SplitBrainProtection {

    private enum SplitBrainProtectionState {
        INITIAL,
        HAS_MIN_CLUSTER_SIZE,
        NO_MIN_CLUSTER_SIZE
    }

    private final NodeEngineImpl nodeEngine;
    private final String splitBrainProtectionName;
    private final int minimumClusterSize;
    private final SplitBrainProtectionConfig config;
    private final EventService eventService;
    private final SplitBrainProtectionFunction splitBrainProtectionFunction;
    private final boolean heartbeatAwareSplitBrainProtectionFunction;
    private final boolean pingAwareSplitBrainProtectionFunction;
    private final boolean membershipListenerSplitBrainProtectionFunction;

    /**
     * Current split brain protection state. Updated by single thread, read by multiple threads.
     */
    private volatile SplitBrainProtectionState splitBrainProtectionState = SplitBrainProtectionState.INITIAL;

    SplitBrainProtectionImpl(SplitBrainProtectionConfig config, NodeEngineImpl nodeEngine) {
        this.nodeEngine = nodeEngine;
        this.eventService = nodeEngine.getEventService();
        this.config = config;
        this.splitBrainProtectionName = config.getName();
        this.minimumClusterSize = config.getMinimumClusterSize();
        this.splitBrainProtectionFunction = initializeSplitBrainProtectionFunction();
        this.heartbeatAwareSplitBrainProtectionFunction = (splitBrainProtectionFunction instanceof HeartbeatAware);
        this.membershipListenerSplitBrainProtectionFunction = (splitBrainProtectionFunction instanceof MembershipListener);
        this.pingAwareSplitBrainProtectionFunction = (splitBrainProtectionFunction instanceof PingAware);
    }

    /**
     * Determines if the split brain protection is present for the given member collection, caches the result and
     * publishes an event under the {@link #splitBrainProtectionName} topic if there was a change in presence.
     * 

* This method is not thread safe and should not be called concurrently. * * @param members the members for which the presence is determined */ void update(Collection members) { SplitBrainProtectionState previousSplitBrainProtectionState = splitBrainProtectionState; SplitBrainProtectionState newSplitBrainProtectionState = SplitBrainProtectionState.NO_MIN_CLUSTER_SIZE; try { boolean present = splitBrainProtectionFunction.apply(members); newSplitBrainProtectionState = present ? SplitBrainProtectionState.HAS_MIN_CLUSTER_SIZE : SplitBrainProtectionState.NO_MIN_CLUSTER_SIZE; } catch (Exception e) { ILogger logger = nodeEngine.getLogger(SplitBrainProtectionService.class); logger.severe("Split brain protection function of split brain protection: " + splitBrainProtectionName + " failed! Split brain protection status is set to " + newSplitBrainProtectionState, e); } if (previousSplitBrainProtectionState == SplitBrainProtectionState.INITIAL && newSplitBrainProtectionState != SplitBrainProtectionState.HAS_MIN_CLUSTER_SIZE) { // We should not set the new quorum state until quorum is met the first time // after local member joins the cluster. return; } splitBrainProtectionState = newSplitBrainProtectionState; if (previousSplitBrainProtectionState == SplitBrainProtectionState.INITIAL) { // We should not fire any quorum events before quorum is present the first time // when local member joins the cluster. return; } if (previousSplitBrainProtectionState != newSplitBrainProtectionState) { createAndPublishEvent(members, newSplitBrainProtectionState == SplitBrainProtectionState.HAS_MIN_CLUSTER_SIZE); } } /** * Notify a {@link HeartbeatAware} {@code SplitBrainProtectionFunction} that a heartbeat has been received from a member. * * @param member source member * @param timestamp heartbeat's timestamp */ void onHeartbeat(Member member, long timestamp) { if (!heartbeatAwareSplitBrainProtectionFunction) { return; } ((HeartbeatAware) splitBrainProtectionFunction).onHeartbeat(member, timestamp); } void onPing(Member member, boolean successful) { if (!pingAwareSplitBrainProtectionFunction) { return; } PingAware pingAware = (PingAware) splitBrainProtectionFunction; if (successful) { pingAware.onPingRestored(member); } else { pingAware.onPingLost(member); } } void onMemberAdded(MembershipEvent event) { if (!membershipListenerSplitBrainProtectionFunction) { return; } ((MembershipListener) splitBrainProtectionFunction).memberAdded(event); } void onMemberRemoved(MembershipEvent event) { if (!membershipListenerSplitBrainProtectionFunction) { return; } ((MembershipListener) splitBrainProtectionFunction).memberRemoved(event); } public String getName() { return splitBrainProtectionName; } public int getMinimumClusterSize() { return minimumClusterSize; } public SplitBrainProtectionConfig getConfig() { return config; } @Override public boolean hasMinimumSize() { return splitBrainProtectionState == SplitBrainProtectionState.HAS_MIN_CLUSTER_SIZE; } /** * Indicates whether the {@link #splitBrainProtectionFunction} is {@link HeartbeatAware}. If so, then member * heartbeats will be published to the {@link #splitBrainProtectionFunction}. * * @return {@code true} when the {@link #splitBrainProtectionFunction} implements {@link HeartbeatAware}, * otherwise {@code false} */ boolean isHeartbeatAware() { return heartbeatAwareSplitBrainProtectionFunction; } /** * Indicates whether the {@link #splitBrainProtectionFunction} is {@link PingAware}. If so, then ICMP pings will be published * to the {@link #splitBrainProtectionFunction}. * * @return {@code true} when the {@link #splitBrainProtectionFunction} implements {@link PingAware}, otherwise {@code false} */ boolean isPingAware() { return pingAwareSplitBrainProtectionFunction; } /** * Returns if split brain protection is needed for this operation. * The split brain protection is determined by the {@link SplitBrainProtectionConfig#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 split brain protection should be consulted for this operation * @throws IllegalArgumentException if the split brain protection configuration type is not handled */ private boolean isSplitBrainProtectionNeeded(Operation op) { SplitBrainProtectionOn type = config.getProtectOn(); switch (type) { case WRITE: return isWriteOperation(op) && shouldCheckSplitBrainProtection(op); case READ: return isReadOperation(op) && shouldCheckSplitBrainProtection(op); case READ_WRITE: return (isReadOperation(op) || isWriteOperation(op)) && shouldCheckSplitBrainProtection(op); default: throw new IllegalStateException("Unhandled split brain protection type: " + type); } } /** * Returns {@code true} if this operation is marked as a read-only operation. * If this method returns {@code false}, the operation still might be * read-only but is not marked as such. */ private static boolean isReadOperation(Operation op) { return op instanceof ReadonlyOperation; } /** * Returns {@code true} if this operation is marked as a mutating operation. * If this method returns {@code false}, the operation still might be * mutating but is not marked as such. */ private static boolean isWriteOperation(Operation op) { return op instanceof MutatingOperation; } /** * Returns {@code true} if the operation allows checking for split brain protection, * {@code false} if the split brain protection check does not apply to this operation. */ private static boolean shouldCheckSplitBrainProtection(Operation op) { return !(op instanceof SplitBrainProtectionCheckAwareOperation) || ((SplitBrainProtectionCheckAwareOperation) op).shouldCheckSplitBrainProtection(); } /** * Ensures that the minimum cluster size property is satisfied (for the purpose of split brain detection) * for the given operation. First checks if the split brain protection type defined by the configuration covers * this operation and checks if the split brain protection is present. * Dispatches an event under the {@link #splitBrainProtectionName} topic * if membership changed after determining the minimum cluster size property is satisfied. * * @param op the operation for which the minimum cluster size property should be satisfied * @throws SplitBrainProtectionException if the operation requires a split brain protection and the * minimum cluster size property is not satisfied. */ void ensureNoSplitBrain(Operation op) { if (!isSplitBrainProtectionNeeded(op)) { return; } ensureNoSplitBrain(); } void ensureNoSplitBrain() { if (!hasMinimumSize()) { throw newSplitBrainProtectionException(); } } private SplitBrainProtectionException newSplitBrainProtectionException() { throw new SplitBrainProtectionException("Split brain protection exception: " + splitBrainProtectionName + " has failed!"); } private void createAndPublishEvent(Collection memberList, boolean presence) { SplitBrainProtectionEvent splitBrainProtectionEvent = new SplitBrainProtectionEvent(nodeEngine.getThisAddress(), minimumClusterSize, memberList, presence); eventService.publishEvent(SplitBrainProtectionServiceImpl.SERVICE_NAME, splitBrainProtectionName, splitBrainProtectionEvent, splitBrainProtectionEvent.hashCode()); } private SplitBrainProtectionFunction initializeSplitBrainProtectionFunction() { SplitBrainProtectionFunction splitBrainProtectionFunction = config.getFunctionImplementation(); if (splitBrainProtectionFunction == null && config.getFunctionClassName() != null) { try { splitBrainProtectionFunction = newInstance(nodeEngine.getConfigClassLoader(), config.getFunctionClassName()); } catch (Exception e) { throw rethrow(e); } } if (splitBrainProtectionFunction == null) { splitBrainProtectionFunction = new MemberCountSplitBrainProtectionFunction(minimumClusterSize); } ManagedContext managedContext = nodeEngine.getSerializationService().getManagedContext(); splitBrainProtectionFunction = (SplitBrainProtectionFunction) managedContext.initialize(splitBrainProtectionFunction); return splitBrainProtectionFunction; } @Override public String toString() { return "SplitBrainProtectionImpl{" + "splitBrainProtectionName='" + splitBrainProtectionName + '\'' + ", isMinimumClusterSizeSatisfied=" + hasMinimumSize() + ", minimumClusterSize=" + minimumClusterSize + ", config=" + config + ", splitBrainProtectionFunction=" + splitBrainProtectionFunction + '}'; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy