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

com.sleepycat.je.rep.impl.RepGroupImpl Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.rep.impl;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.JEVersion;
import com.sleepycat.je.rep.MemberNotFoundException;
import com.sleepycat.je.rep.impl.RepGroupDB.NodeBinding;

/**
 * Represents a snapshot of the Replication Group as a whole. Note that
 * membership associated with a group is dynamic and its constituents can
 * change at any time. It's useful to keep in mind that due to the distributed
 * nature of the Replication Group all the nodes in a replication group may not
 * have the same consistent picture of the replication group at a single point
 * in time, but will converge to become consistent eventually.
 */
public class RepGroupImpl {

    /** The latest supported format version. */
    public static final int MAX_FORMAT_VERSION = 3;

    /**
     * Format version introduced in JE 6.0.1 that records a node's most recent
     * JE version, and the minimum JE version required to join the group.
     */
    public static final int FORMAT_VERSION_3 = 3;

    /**
     * The latest format version that is compatible with JE 6.0.0 and earlier
     * versions.
     */
    public static final int FORMAT_VERSION_2 = 2;

    /** The initial format version for newly created RepGroupImpl instances. */
    public static final int INITIAL_FORMAT_VERSION = 3;

    /** The oldest supported format version. */
    static final int MIN_FORMAT_VERSION = 2;

    /** The first JE version that supports FORMAT_VERSION_3. */
    public static final JEVersion FORMAT_VERSION_3_JE_VERSION =
        new JEVersion("6.0.1");

    /**
     * The first JE version that supports the oldest supported format version.
     */
    public static final JEVersion MIN_FORMAT_VERSION_JE_VERSION =
        new JEVersion("5.0.0");

    /** The initial change version. */
    private final static int CHANGE_VERSION_START = 0;

    /*
     * The special UUID associated with a group, when the group UUID is unknown
     * because a node is still in the process of joining the group. This value
     * cannot be created by UUID.randomUUID
     */
    private final static UUID UNKNOWN_UUID = new UUID(0, 0);

    /**
     * The maximum number of nodes with transient ID that can join the group at
     * the same time time.  This number of transient id node IDs will be
     * reserved at the top of the node ID range.
     */
    public static final int MAX_NODES_WITH_TRANSIENT_ID = 1024;

    /** The first node ID for persistent nodes. */
    private static final int NODE_SEQUENCE_START = 0;

    /** The maximum node ID for persistent nodes. */
    private static final int NODE_SEQUENCE_MAX =
        Integer.MAX_VALUE - MAX_NODES_WITH_TRANSIENT_ID;

    /** Returns true if the node is electable. */
    private static final Predicate ELECTABLE_PREDICATE = new Predicate() {
        @Override
        boolean include(final RepNodeImpl n) {
            return n.getType().isElectable();
        }
    };

    /** Returns true if the node is a monitor. */
    private static final Predicate MONITOR_PREDICATE = new Predicate() {
        @Override
        boolean include(final RepNodeImpl n) {
            return n.getType().isMonitor();
        }
    };

    /** Returns true if the node is secondary. */
    private static final Predicate SECONDARY_PREDICATE = new Predicate() {
        @Override
        boolean include(final RepNodeImpl n) {
            return n.getType().isSecondary();
        }
    };

    /** Returns true if the node is external. */
    private static final Predicate EXTERNAL_PREDICATE = new Predicate() {
        @Override
        boolean include(final RepNodeImpl n) {
            return n.getType().isExternal();
        }
    };

    /** Returns true if the node can return acks but is not an Arbiter. */
    private static final Predicate ACK_PREDICATE = new Predicate() {
        @Override
        boolean include(final RepNodeImpl n) {
            return n.getType().isElectable() && !n.getType().isArbiter();
        }
    };

    /** Returns true if the node is an arbiter. */
    private static final Predicate ARBITER_PREDICATE = new Predicate() {
        @Override
        boolean include(final RepNodeImpl n) {
            return n.getType().isArbiter();
        }
    };

    /* The name of the Replication Group. */
    private final String groupName;

    /*
     * The universally unique UUID associated with the replicated environment.
     */
    private UUID uuid;

    /*
     * The version number associated with this group's format in the database.
     */
    private volatile int formatVersion;

    /*
     * Tracks the change version level. It's updated with every change to the
     * member set in the membership database.
     */
    private int changeVersion = 0;

    /*
     * The most recently assigned node ID for persistent nodes.  Node IDs for
     * persistent nodes are never reused.
     */
    private int nodeIdSequence;

    /*
     * The following maps represent the set of nodes in the group indexed in
     * two different ways: by user-defined node name and by internal id. Note
     * that both maps contain nodes that are no longer members of the group.
     *
     * All access to nodesById and nodesByName should be synchronized on
     * nodesById, to avoid ConcurrentModificationException and to provide
     * consistent results for both maps.
     */

    /* All the nodes that form the replication group, indexed by Id. */
    private final Map nodesById =
        new HashMap();

    /*
     * All the nodes that form the replication group, indexed by node name.
     * This map is used exclusively for efficient lookups by name. The map
     * nodesById does all the heavy lifting.
     */
    private final Map nodesByName =
        new HashMap();

    /** The minimum JE version required for nodes to join the group. */
    private volatile JEVersion minJEVersion = MIN_FORMAT_VERSION_JE_VERSION;

    /**
     * Constructor to create a new empty repGroup, typically as part of
     * environment initialization.
     *
     * @param groupName the group name
     * @param currentJEVersion if non-null, override the current JE version,
     * for testing
     */
    public RepGroupImpl(String groupName, JEVersion currentJEVersion) {
        this(groupName, false, currentJEVersion);
    }

    /**
     * Constructor to create a group and specify if the group's UUID should be
     * unknown or generated randomly.
     */
    public RepGroupImpl(String groupName,
                        boolean unknownUUID,
                        JEVersion currentJEVersion) {
        this(groupName,
             unknownUUID ? UNKNOWN_UUID : UUID.randomUUID(),
             getCurrentFormatVersion(currentJEVersion));
    }

    /** Get the current format version, supporting a test override. */
    private static int getCurrentFormatVersion(
        final JEVersion currentJEVersion) {

        return (currentJEVersion == null) ?
            MAX_FORMAT_VERSION :
            getMaxFormatVersion(currentJEVersion);
    }

    /**
     * Constructor to create a group and specify the group's UUID and format
     * version.
     */
    public RepGroupImpl(String groupName, UUID uuid, int formatVersion) {
        this(groupName,
             uuid,
             formatVersion,
             CHANGE_VERSION_START,
             NODE_SEQUENCE_START,
             ((formatVersion < FORMAT_VERSION_3) ?
              MIN_FORMAT_VERSION_JE_VERSION :
              FORMAT_VERSION_3_JE_VERSION));
    }

    /**
     * Constructor used to recreate an existing RepGroup, typically as part of
     * a deserialization operation.
     *
     * @param groupName
     * @param uuid
     * @param formatVersion
     * @param changeVersion
     * @param minJEVersion
     */
    public RepGroupImpl(String groupName,
                        UUID uuid,
                        int formatVersion,
                        int changeVersion,
                        int nodeIdSequence,
                        JEVersion minJEVersion) {
        this.groupName = groupName;
        this.uuid = uuid;
        this.formatVersion = formatVersion;
        this.changeVersion = changeVersion;
        setNodeIdSequence(nodeIdSequence);
        this.minJEVersion = minJEVersion;

        if (formatVersion < MIN_FORMAT_VERSION ||
            formatVersion > MAX_FORMAT_VERSION) {
            throw new IllegalStateException(
                "Expected membership database format version between: " +
                MIN_FORMAT_VERSION + " and " + MAX_FORMAT_VERSION +
                ", encountered unsupported version: " + formatVersion);
        }
        if (minJEVersion == null) {
            throw new IllegalArgumentException(
                "The minJEVersion must not be null");
        }
    }

    /*
     * Returns true if the UUID has not as yet been established at this node.
     * This is the case when a knew node first joins a group, and it has not
     * as yet replicated the group database via the replication stream.
     */
    public boolean hasUnknownUUID() {
        return UNKNOWN_UUID.equals(uuid);
    }

    /**
     * Predicate to help determine whether the UUID is the canonical unknown
     * UUID.
     */
    public static boolean isUnknownUUID(UUID uuid) {
        return UNKNOWN_UUID.equals(uuid);
    }

    /**
     * Sets the UUID. The UUID can only be set if it's currently unknown.
     */
    public void setUUID(UUID uuid) {
        if (!hasUnknownUUID()) {
            throw EnvironmentFailureException.unexpectedState
                ("Expected placeholder UUID, not " + uuid);
        }
        this.uuid = uuid;
    }

    /**
     * Removes a member transiently from the rep group by marking it as removed
     * and optionally deleting it from the by-name and by-ID maps. This action
     * is usually a precursor to making the change persistent on disk.
     *
     * @param nodeName identifies the node being removed
     *
     * @param delete whether to delete the node from the maps
     *
     * @return the node that was removed
     *
     * @throws EnvironmentFailureException if the node is not part of the group
     * or is a node with a transient ID
     */
    public RepNodeImpl removeMember(final String nodeName,
                                    final boolean delete) {
        final RepNodeImpl node = getMember(nodeName);
        if (node == null) {
            throw EnvironmentFailureException.unexpectedState
                ("Node:" + nodeName + " is not a member of the group.");
        }
        if (node.getType().hasTransientId()) {
            throw EnvironmentFailureException.unexpectedState(
                "Cannot remove node with transient id: " + nodeName);
        }
        if (delete) {
            synchronized (nodesById) {
                nodesById.remove(node.getNodeId());
                nodesByName.remove(nodeName);
            }
        }
        node.setRemoved(true);
        return node;
    }

    /**
     * Checks for whether a new or changed node definition is in conflict with
     * other members of the group.  In particular, checks that the specified
     * node does not use the same socket address as another member.
     * 

* This check must be done when adding a new member to the group, or * changing the network address of an existing member, and must be done * with the rep group entry in the database locked for write to prevent * race conditions. * * @param node the new node that is being checked for conflicts * @throws NodeConflictException if there is a conflict */ public void checkForConflicts(RepNodeImpl node) throws DatabaseException, NodeConflictException { for (RepNodeImpl n : getAllMembers(null)) { if (n.getNameIdPair().equals(node.getNameIdPair())) { continue; } if (n.getSocketAddress().equals(node.getSocketAddress())) { throw new NodeConflictException ("New or moved node:" + node.getName() + ", is configured with the socket address: " + node.getSocketAddress() + ". It conflicts with the socket already " + "used by the member: " + n.getName()); } } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + changeVersion; result = prime * result + ((groupName == null) ? 0 : groupName.hashCode()); synchronized (nodesById) { result = prime * result + nodesById.hashCode(); } /* Don't bother with nodesByName */ result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); result = prime * result + formatVersion; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof RepGroupImpl)) { return false; } RepGroupImpl other = (RepGroupImpl) obj; if (changeVersion != other.changeVersion) { return false; } if (groupName == null) { if (other.groupName != null) { return false; } } else if (!groupName.equals(other.groupName)) { return false; } /* Don't bother with nodesByName, since nodesById equality covers it */ if (uuid == null) { if (other.uuid != null) { return false; } } else if (!uuid.equals(other.uuid)) { return false; } if (formatVersion != other.formatVersion) { return false; } if (!minJEVersion.equals(other.minJEVersion)) { return false; } /* * Do this last, since it is expensive because of its need to avoid * concurrency conflicts. */ final Map otherNodesById; synchronized (other.nodesById) { otherNodesById = new HashMap(other.nodesById); } synchronized (nodesById) { if (!nodesById.equals(otherNodesById)) { return false; } } return true; } /** * Sets the nodes associated with the Rep group. Note that both nodesById * and nodesByIndex are initialized. */ public void setNodes(final Map nodes) { synchronized (nodesById) { /* Remove nodes with persistent id */ for (final Iterator iter = nodesById.values().iterator(); iter.hasNext(); ) { final RepNodeImpl node = iter.next(); if (!node.getType().hasTransientId()) { iter.remove(); nodesByName.remove(node.getName()); } } /* Add specified nodes */ if (nodes != null) { for (final RepNodeImpl node : nodes.values()) { final RepNodeImpl prevById = nodesById.put(node.getNodeId(), node); final RepNodeImpl prevByName = nodesByName.put(node.getName(), node); /* * Also remove entries for any previous nodes if the * mapping between names and IDs was changed. */ if ((prevById != null) && !node.getName().equals(prevById.getName())) { nodesByName.remove(prevById.getName()); } if ((prevByName != null) && node.getNodeId() != prevByName.getNodeId()) { nodesById.remove(prevByName.getNodeId()); } } } assert new HashSet<>(nodesById.values()).equals( new HashSet<>(nodesByName.values())) : "Node maps indexed by ID and name differ: " + "IDs: " + nodesById + ", Names: " + nodesByName; } } /** * Add a node with transient id. The caller should already have assigned * the node an ID and checked that the replication group supports secondary * nodes. * * @param node the node with transient id * @throws IllegalStateException if the store does not currently support * secondary nodes * @throws NodeConflictException if the node conflicts with an existing * persistent node */ public void addTransientIdNode(final RepNodeImpl node) { if (!node.getType().hasTransientId()) { throw new IllegalArgumentException( "Attempt to call addTransientIdNode on a node without " + "transient id: " + node); } if (node.getNameIdPair().hasNullId()) { throw new IllegalArgumentException( "Attempt to call addTransientIdNode on node without ID: " + node); } synchronized (nodesById) { final RepNodeImpl prevById = nodesById.get(node.getNodeId()); assert (prevById == null) || prevById.getType().hasTransientId() : "Same node ID for nodes with transient and persistent ID: " + node + ", " + prevById; final RepNodeImpl prevByName = nodesByName.get(node.getName()); if ((prevByName != null) && !prevByName.getType().hasTransientId()) { throw new NodeConflictException( "New node with transient ID " + node.getName() + " conflicts with an existing node with persistent ID" + " with the same name: " + prevByName); } final RepNodeImpl prevById2 = nodesById.put(node.getNodeId(), node); assert prevById == prevById2; final RepNodeImpl prevByName2 = nodesByName.put(node.getName(), node); assert prevByName == prevByName2; if ((prevById != null) && !node.getName().equals(prevById.getName())) { nodesByName.remove(prevById.getName()); } if ((prevByName != null) && (node.getNodeId() != prevByName.getNodeId())) { nodesById.remove(prevByName.getNodeId()); } assert new HashSet<>(nodesById.values()).equals( new HashSet<>(nodesByName.values())) : "Node maps indexed by ID and name differ: " + "IDs: " + nodesById + ", Names: " + nodesByName; } } /** * Remove a node with transient id, which should have an assigned ID * * @param node the node with a transient ID */ public void removeTransientNode(final RepNodeImpl node) { if (!node.getType().hasTransientId()) { throw new IllegalArgumentException( "Attempt to call removeTransientNode on a" + " node without transient ID: " + node); } if (node.getNameIdPair().hasNullId()) { throw new IllegalArgumentException( "Attempt to call removeTransientNode on a node with no ID: " + node); } synchronized (nodesById) { nodesById.remove(node.getNodeId()); nodesByName.remove(node.getName()); } } /** * returns the unique UUID associated with the replicated environment. * * @return the UUID */ public UUID getUUID() { return uuid; } /** * Returns the version of the format (the schema) in use by this group * instance in the database. * * @return the format version */ public int getFormatVersion() { return formatVersion; } /** * Returns the highest format version supported by the specified JE * version. * * @param jeVersion the JE version * @return the highest format version supported by that JE version */ public static int getMaxFormatVersion(final JEVersion jeVersion) { if (jeVersion.compareTo(FORMAT_VERSION_3_JE_VERSION) < 0) { return FORMAT_VERSION_2; } return FORMAT_VERSION_3; } /** * Returns the version of the instance as represented by changes to the * members constituting the group. * * @return the object change version */ public int getChangeVersion() { return changeVersion; } /** * Increments the object change version. It must be called with the group * entry locked in the group database. * * @return the incremented change version */ public int incrementChangeVersion() { return ++changeVersion; } /** * Returns the current highest node ID currently in use by the group. * * @return the highest node ID in use */ public int getNodeIdSequence() { return nodeIdSequence; } /** * Set the node id sequence. This is only done in unusual circumstances, * e.g. when a replication group is being reset in an existing replicated * environment and we want to ensure that the internal node ids are not * reused in the logs. */ public void setNodeIdSequence(int nodeIdSequence) { if (nodeIdSequence < 0 || nodeIdSequence > NODE_SEQUENCE_MAX) { throw new IllegalArgumentException( "Bad nodeIdSequence: " + nodeIdSequence); } this.nodeIdSequence = nodeIdSequence; } /** * Increments the node ID sequence and returns it. * * @return the next node ID for use in a new node */ public int getNextNodeId() { if (nodeIdSequence >= NODE_SEQUENCE_MAX) { throw new IllegalStateException("Reached maximum node ID"); } return ++nodeIdSequence; } /** * Returns the node ID that is associated with the very first node in the * replication group. */ public static int getFirstNodeId() { return NODE_SEQUENCE_START + 1; } /** * Returns the minimum JE version that a node must be running in order to * join the group. */ public JEVersion getMinJEVersion() { return minJEVersion; } /** * Sets the minimum JE version that a node must be running in order to join * the replication group. The group object should have had its nodes * fetched using the {@link RepGroupDB#fetchGroup} method and should be * stored to the group database after making this change. Throws a {@link * MinJEVersionUnsupportedException} if the requested version is not * supported. Updates the group format version as needed to match the JE * version. Has no effect if the current minimum value is already as high * or higher than the requested one. * * @param newMinJEVersion the new minimum JE version * @throws MinJEVersionUnsupportedException if the requested version is not * supported by the group's electable nodes */ public void setMinJEVersion(final JEVersion newMinJEVersion) throws MinJEVersionUnsupportedException { if (newMinJEVersion == null) { throw new IllegalArgumentException( "The newMinJEVersion argument must not be null"); } if (newMinJEVersion.compareTo(minJEVersion) <= 0) { return; } final int newFormatVersion = getMaxFormatVersion(newMinJEVersion); /* Minimum JE version is not stored before format version 3 */ if (newFormatVersion < FORMAT_VERSION_3) { return; } for (final RepNodeImpl node : getElectableMembers()) { final JEVersion nodeJEVersion = node.getJEVersion(); if ((nodeJEVersion != null) && nodeJEVersion.compareTo(newMinJEVersion) < 0) { throw new MinJEVersionUnsupportedException( newMinJEVersion, node.getName(), nodeJEVersion); } } minJEVersion = newMinJEVersion; formatVersion = newFormatVersion; } /** * Used to ensure that the ReplicationGroup value is consistent after it * has been fetched via a readUncommitted access to the rep group database. * It does so by ensuring that the summarized values match the nodes that * were actually read. */ public void makeConsistent() { synchronized (nodesById) { if (nodesById.isEmpty()) { return; } int computedNodeId = NODE_SEQUENCE_START-1; int computedChangeVersion = -1; for (RepNodeImpl mi : nodesById.values()) { /* Get the highest node ID */ if (computedNodeId < mi.getNodeId()) { computedNodeId = mi.getNodeId(); } /* Get the highest change version. */ if (computedChangeVersion < mi.getChangeVersion()) { computedChangeVersion = mi.getChangeVersion(); } } setNodeIdSequence(computedNodeId); changeVersion = computedChangeVersion; } } /* * Serialization */ /** * Serializes an object by converting its TupleBinding byte based * representation into the hex characters denoting the bytes. * * @param the type of the object being serialized * @param binding the tuble binding used to convert it into its byte form * @param object the object being serialized * @return the hex string containing the serialized hex form of the object */ static String objectToHex(TupleBinding binding, T object) { StringBuilder buffer = new StringBuilder(); TupleOutput tuple = new TupleOutput(new byte[100]); binding.objectToEntry(object, tuple); byte[] bytes = tuple.getBufferBytes(); int size = tuple.getBufferLength(); for (int i = 0; i < size; i++) { int lowNibble = (bytes[i] & 0xf); int highNibble = ((bytes[i]>>4) & 0xf); buffer.append(Character.forDigit(lowNibble, 16)); buffer.append(Character.forDigit(highNibble, 16)); } return buffer.toString(); } /** * Returns a serialized character based form of the group suitable for use * in subclasses of SimpleProtocol. The serialized form is a multi-token * string. The first token represents the RepGroup object itself with each * subsequent node representing a node in the group. Tokens are separated * by '|', the protocol separator character. The number of tokens is thus * equal to the number of nodes in the group + 1. Each token is itself a * hex character based representation of the binding used to serialize a * RepGroup and store it into the database. * * @param groupFormatVersion the group format version * @return the string encoded as above */ public String serializeHex(final int groupFormatVersion) { final RepGroupDB.GroupBinding groupBinding = new RepGroupDB.GroupBinding(groupFormatVersion); StringBuilder buffer = new StringBuilder(); buffer.append(objectToHex(groupBinding, this)); synchronized (nodesById) { for (RepNodeImpl mi : nodesById.values()) { /* * Only include nodes that can be serialized with the specified * format version */ if (NodeBinding.supportsObjectToEntry( mi, groupFormatVersion)) { buffer.append(TextProtocol.SEPARATOR); buffer.append(serializeHex(mi, groupFormatVersion)); } } } return buffer.toString(); } /** * Returns the serialized form of the node as a sequence of hex characters * suitable for use by the text based protocols. * * @param node the node to be serialized. * @param formatVersion the group format version * @return the string containing the serialized form of the node */ public static String serializeHex(final RepNodeImpl node, final int formatVersion) { final NodeBinding nodeBinding = new NodeBinding(formatVersion); return objectToHex(nodeBinding, node); } /** * Serialize the node into its byte representation. * * @param node the node to be serialized * @param formatVersion the group format version * @return the serialized byte array */ public static byte[] serializeBytes(final RepNodeImpl node, final int formatVersion) { final NodeBinding binding = new NodeBinding(formatVersion); final TupleOutput tuple = new TupleOutput(new byte[NodeBinding.APPROX_MAX_SIZE]); binding.objectToEntry(node, tuple); return tuple.getBufferBytes(); } /** * Deserializes the object serialized by {@link #serializeHex} * * @param hex the string containing the serialized form of the node * @param formatVersion the group format version * * @return the de-serialized object */ public static RepNodeImpl hexDeserializeNode(final String hex, final int formatVersion) { final NodeBinding nodeBinding = new NodeBinding(formatVersion); return hexToObject(nodeBinding, hex); } /** * Deserialize the mode from its byte representation. * * @param bytes the byte representation of the node. * @param formatVersion the group format version * * @return the deserialized object */ public static RepNodeImpl deserializeNode(final byte[] bytes, final int formatVersion) { final NodeBinding binding = new NodeBinding(formatVersion); TupleInput tuple = new TupleInput(bytes); return binding.entryToObject(tuple); } /** * Carries out the two step de-serialization from hex string into a byte * buffer and subsequently into its object representation. * * @return the object representation */ private static T hexToObject(TupleBinding binding, String hex) { byte buffer[] = new byte[(hex.length() / 2)]; for (int i = 0; i < hex.length(); i += 2) { int value = Character.digit(hex.charAt(i), 16); value |= Character.digit(hex.charAt(i + 1), 16) << 4; buffer[i >> 1] = (byte)value; } TupleInput tuple = new TupleInput(buffer); return binding.entryToObject(tuple); } /** * De-serializes an array of tokens into a Rep group object and its nodes. * the token at startrepresents the group object and each * subsequent token represents a node in the group. * * @param tokens the array representing the group and its nodes * @param start the position in the array at which to start the * de-serialization. * * @return the de-serialized RepGroup */ static public RepGroupImpl deserializeHex(String[] tokens, int start) { final RepGroupDB.GroupBinding groupBinding = new RepGroupDB.GroupBinding(); RepGroupImpl group = hexToObject(groupBinding, tokens[start++]); Map nodeMap = new HashMap(); while (start < tokens.length) { RepNodeImpl n = hexDeserializeNode(tokens[start++], group.getFormatVersion()); RepNodeImpl old = nodeMap.put(n.getNameIdPair().getId(), n); assert(old == null); } group.setNodes(nodeMap); return group; } /* * Accessing nodes and groups of nodes */ /** * Returns the node IDs for all nodes that are currently members of the * group and that act as proposers, acceptors, or distinguished learners. * Returns IDs for all ELECTABLE and MONITOR nodes that are not removed, * even if they are not acknowledged, but not for SECONDARY or EXTERNAL * nodes. */ public Set getAllElectionMemberIds() { Set ret = new HashSet(); synchronized (nodesById) { for (RepNodeImpl mi : nodesById.values()) { if (!mi.isRemoved() && !mi.getType().hasTransientId()) { ret.add(mi.getNodeId()); } } } return ret; } /** * Returns all nodes that are currently members of the group. Returns all * ELECTABLE and MONITOR nodes that are acknowledged and not removed, * and SECONDARY and EXTERNAL nodes. If the predicate is * not null, only includes members that satisfy the predicate. */ public Set getAllMembers(final Predicate p) { final Set result = new HashSet(); includeMembers(p, result); return result; } /** * Adds all nodes that are currently members of the group to the specified * set. Adds all ELECTABLE and MONITOR nodes that are not removed, even if * they are not acknowledged, and SECONDARY and EXTERNAL nodes. If the * predicate is not null, only adds members that satisfy the predicate. */ public void includeAllMembers(final Predicate p, final Set set) { synchronized (nodesById) { for (RepNodeImpl mi : nodesById.values()) { if (!mi.isRemoved() && ((p == null) || p.include(mi))) { set.add(mi); } } } } /** * Counts the number of nodes that are currently members of the group. * Counts all ELECTABLE and MONITOR nodes that are not removed, even if * they are not acknowledged, and SECONDARY and EXTERNAL nodes. If the * predicate is not null, only counts members that satisfy the predicate. */ public int countAllMembers(final Predicate p) { int count = 0; synchronized (nodesById) { for (final RepNodeImpl mi : nodesById.values()) { if (!mi.isRemoved() && ((p == null) || p.include(mi))) { count++; } } } return count; } /** * Adds nodes that are currently members of the group to the specified set. * Adds ELECTABLE and MONITOR node that are not removed and are * acknowledged, and SECONDARY and EXTERNAL nodes. If the predicate is not * null, only adds members that satisfy the predicate. */ public void includeMembers(final Predicate p, final Set set) { synchronized (nodesById) { for (RepNodeImpl n : nodesById.values()) { if (!n.isRemoved() && n.isQuorumAck() && ((p == null) || p.include(n))) { set.add(n); } } } } /** * Gets the node that is currently a member of the group that has the given * socket address. Returns ELECTABLE and MONITOR nodes that are not * removed, even if it is not acknowledged, and SECONDARY and EXTERNAL * nodes. * * @return the desired node, or null if there is no such node, including * if it was removed */ public RepNodeImpl getMember(InetSocketAddress socket) { synchronized (nodesById) { for (RepNodeImpl n : nodesById.values()) { if (socket.equals(n.getSocketAddress()) && !n.isRemoved()) { return n; } } } return null; } /** * Returns nodes that are removed from the group. Returns ELECTABLE and * MONITOR nodes that are removed and are acknowledged, but not SECONDARY * or EXTERNAL nodes, which are not remembered when they are removed. */ public Set getRemovedNodes() { Set ret = new HashSet(); synchronized (nodesById) { for (RepNodeImpl mi : nodesById.values()) { if (mi.isRemoved() && mi.isQuorumAck()) { ret.add(mi); } } } return ret; } /** A predicate for specifying which replication nodes to include. */ abstract static class Predicate { abstract boolean include(RepNodeImpl n); } /** * Returns all electable nodes that are currently members of the group. * Returns all ELECTABLE nodes that are not removed, even if they are not * acknowledged, but not MONITOR, SECONDARY, or EXTERNAL nodes. */ public Set getAllElectableMembers() { return getAllMembers(ELECTABLE_PREDICATE); } /** * Returns electable nodes that are currently members of the group. * Returns ELECTABLE nodes that are not removed and are acknowledged, but * not MONITOR, SECONDARY, or EXTERNAL nodes. */ public Set getElectableMembers() { final Set result = new HashSet(); includeElectableMembers(result); return result; } /** * Adds the electable nodes that are currently members of the group to the * specified set. Adds ELECTABLE nodes that are not removed and are * acknowledged, but not MONITOR, SECONDARY, or EXTERNAL nodes. */ public void includeElectableMembers(final Set set) { includeAllMembers( new Predicate() { @Override boolean include(RepNodeImpl n) { return n.getType().isElectable() && n.isQuorumAck(); } }, set); } /** * Returns the nodes that are currently members of the group that store * replication data. Returns ELECTABLE nodes that are not removed and are * acknowledged, and SECONDARY nodes, but not MONITOR or EXTERNAL nodes. */ public Set getDataMembers() { final Set result = new HashSet(); includeDataMembers(result); return result; } /** * Adds the nodes that are currently members of the group that store * replication data to the specified set. Adds ELECTABLE nodes that are * not removed and are acknowledged, and SECONDARY nodes, but not MONITOR * or EXTERNAL nodes. */ public void includeDataMembers(final Set set) { includeAllMembers( new Predicate() { @Override boolean include(final RepNodeImpl n) { return n.getType().isDataNode() && n.isQuorumAck(); } }, set); } /** * Returns the monitor nodes that are currently members of the group. * Returns MONITOR nodes that are not removed and are acknowledged, but not * ELECTABLE, SECONDARY, or EXTERNAL nodes. * * @return the set of monitor nodes */ public Set getMonitorMembers() { final Set result = new HashSet(); includeMonitorMembers(result); return result; } /** * Adds the monitor nodes that are currently members of the group to the * specified set. Adds MONITOR nodes that are not removed and are * acknowledged, but not ELECTABLE, SECONDARY, or EXTERNAL nodes. */ public void includeMonitorMembers(final Set set) { includeMembers(MONITOR_PREDICATE, set); } /** * Returns all the nodes that are currently members of the group that act * as distinguished learners to receive election results. Returns all * ELECTABLE and MONITOR that are not removed, even if they are not * acknowledged, but not SECONDARY or EXTERNAL nodes. */ public Set getAllLearnerMembers() { final Set result = new HashSet(); includeAllMembers( new Predicate() { @Override boolean include(final RepNodeImpl n) { return (n.getType().isElectable() || n.getType().isMonitor()); } }, result); return result; } /** * Returns the secondary nodes that are currently members of the group. * * Returns SECONDARY nodes, but not ELECTABLE, MONITOR, or EXTERNAL nodes. */ public Set getSecondaryMembers() { final Set result = new HashSet<>(); includeSecondaryMembers(result); return result; } /** * Returns the external nodes that are currently members of the group. * * Returns EXTERNAL nodes, but not ELECTABLE, MONITOR, or SECONDARY nodes. */ public Set getExternalMembers() { final Set result = new HashSet<>(); includeExternalMembers(result); return result; } /** * Adds the secondary nodes that are currently members of the group to the * specified set. Adds SECONDARY nodes, but not ELECTABLE, MONITOR, or * EXTERNAL nodes. */ public void includeSecondaryMembers(final Set set) { includeAllMembers(SECONDARY_PREDICATE, set); } /** * Adds the external nodes. Adds EXTERNAL nodes, but not ELECTABLE, * MONITOR, or SECONDARY nodes. */ public void includeExternalMembers(final Set set) { includeAllMembers(EXTERNAL_PREDICATE, set); } /** * Returns the arbiter nodes that are currently members of the group. * Returns ARBITER nodes. */ public Set getArbiterMembers() { final Set result = new HashSet(); includeArbiterMembers(result); return result; } /** * Adds the arbiter nodes that are currently members of the group to the * specified set. Adds ARBITER nodes. */ public void includeArbiterMembers(final Set set) { includeMembers(ARBITER_PREDICATE, set); } /** * Returns the socket addresses for all nodes that are currently members of * the group. Returns addresses for all ELECTABLE and MONITOR nodes that * are not removed, even if they are not acknowledged, and for all * nodes with transient id. If the predicate is not null, only returns * addresses for members that satisfy the predicate. ARBITER nodes are * also ELECTABLE and will be part of the returned set. */ private Set getAllMemberSockets(Predicate p) { Set sockets = new HashSet(); synchronized (nodesById) { for (final RepNodeImpl mi : nodesById.values()) { if ((((mi.getType().isElectable() || mi.getType().isMonitor()) && !mi.isRemoved()) || mi.getType().hasTransientId()) && ((p == null) || p.include(mi))) { sockets.add(mi.getSocketAddress()); } } } return sockets; } /** * Return the socket addresses for all nodes that are currently members of * the group and act as distinguished learners to receive election results. * Returns addresses for all ELECTABLE and MONITOR nodes that are not * removed, even if they are not acknowledged, but not for SECONDARY or * EXTERNAL nodes. * * @return set of learner socket addresses */ public Set getAllLearnerSockets() { /* * TODO: Consider including secondary nodes in this list. * That change would increase the chance that SECONDARY nodes have * up-to-date information about the master, but would need to be * paired with a change to only wait for delivery of notifications to * ELECTABLE nodes, to avoid adding sensitivity to potentially longer * network delays in communicating with secondary nodes. */ return getAllMemberSockets(new Predicate() { @Override boolean include(RepNodeImpl n) { return !n.getType().isSecondary() && !n.getType().isExternal(); } }); } /** * Return the socket addresses for all nodes that are currently members of * the group and act as helpers to supply election results. Returns * addresses for all ELECTABLE and MONITOR nodes that are not removed, even * if they are not acknowledged, and SECONDARY nodes. * * @return set of helper socket addresses */ public Set getAllHelperSockets() { return getAllMemberSockets(null); } /** * Returns the socket addresses for all monitor nodes that are currently * members of the group. Returns addresses for all MONITOR nodes that are * not removed, even if they are not acknowledged, but not for ELECTABLE, * SECONDARY, or EXTERNAL nodes. * * @return the set of Monitor socket addresses */ public Set getAllMonitorSockets() { return getAllMemberSockets(MONITOR_PREDICATE); } /** * Returns the socket addresses for all nodes that are currently members of * the group and act as acceptors for elections. Returns addresses for all * ELECTABLE nodes that are not removed, even if they are not acknowledged, * but not for MONITOR, SECONDARY, or EXTERNAL nodes, which do not act as * acceptors. * * @return the set of acceptor socket addresses */ public Set getAllAcceptorSockets() { return getAllMemberSockets(ELECTABLE_PREDICATE); } /** * Returns the node with the specified ID that is currently a member of the * group, throwing an exception if the node is found but is no longer a * member. Returns ELECTABLE and MONITOR nodes that are not removed, even * if they are not acknowledged, and SECONDARY and EXTERNAL nodes. * * @param nodeId the node ID * @return the member or null * @throws EnvironmentFailureException if the node is no longer a member */ public RepNodeImpl getMember(int nodeId) { RepNodeImpl node = getNode(nodeId); if (node == null) { return null; } if (node.isRemoved()) { throw EnvironmentFailureException.unexpectedState ("No longer a member:" + nodeId); } return node; } /** * Returns the node with the specified name that is currently a member of * the group, throwing an exception if the node is found but is no longer a * member. Returns ELECTABLE and MONITOR nodes that are not removed, even * if they are not acknowledged, and SECONDARY and EXTERNAL nodes. * * @param name the node name * @return the member or null * @throws MemberNotFoundException if the node is no longer a member */ public RepNodeImpl getMember(String name) throws MemberNotFoundException { RepNodeImpl node = getNode(name); if (node == null) { return null; } if (node.isRemoved()) { throw new MemberNotFoundException ("Node no longer a member:" + name); } return node; } /** * Returns the node with the specified ID, regardless of its membership * state. Returns all ELECTABLE and MONITOR nodes, even if they are * removed or are not acknowledged, and SECONDARY and EXTERNAL nodes. * * @return the node or null */ public RepNodeImpl getNode(int nodeId) { synchronized (nodesById) { return nodesById.get(nodeId); } } /** * Returns the node with the specified name, regardless of its membership * state. Returns all ELECTABLE and MONITOR nodes, even if they are * removed or are not acknowledged, and SECONDARY and EXTERNAL nodes. * * @return the node or null */ public RepNodeImpl getNode(String name) { synchronized (nodesById) { return nodesByName.get(name); } } /** * Returns the number of all electable nodes that are currently members of * the group. Includes all ELECTABLE nodes that are not removed, even if * they are not acknowledged, but not MONITOR, SECONDARY, or EXTERNAL * nodes. Note that even unACKed nodes are considered part of the group * for group size/durability considerations. * * @return the size of the group for durability considerations */ public int getElectableGroupSize() { return countAllMembers(ELECTABLE_PREDICATE); } /** * Returns the number of all electable nodes that are currently members of * the group. Includes all ELECTABLE nodes that are not removed, even if * they are not acknowledged, but not MONITOR, ARBITER, SECONDARY, or * EXTERNAL nodes. Note that even unACKed nodes are considered part of the * group for group size/durability considerations. * * @return the size of the group for durability considerations */ public int getAckGroupSize() { return countAllMembers(ACK_PREDICATE); } /* Miscellaneous */ /** * Returns the name of the group. * * @return the name of the group. */ public String getName() { return groupName; } /* * An internal exception indicating that two nodes have conflicting * configurations. For example, they both use the same hostname and port. */ @SuppressWarnings("serial") public static class NodeConflictException extends DatabaseException { public NodeConflictException(String message) { super(message); } } /** * Return information to the user, format nicely for ease of reading. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Group info [").append(groupName).append("] "); sb.append(getUUID()). append("\n Format version: ").append(getFormatVersion()). append("\n Change version: ").append(getChangeVersion()). append("\n Max persist rep node ID: ").append(getNodeIdSequence()). append("\n Min JE version: ").append(minJEVersion). append("\n"); synchronized (nodesById) { for (final RepNodeImpl node : nodesById.values()) { sb.append(" ").append(node); } } return sb.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy