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

oracle.kv.impl.api.Request Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

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

package oracle.kv.impl.api;

import static oracle.kv.impl.util.ObjectUtil.checkNull;
import static oracle.kv.impl.util.SerialVersion.STD_UTF8_VERSION;
import static oracle.kv.impl.util.SerializationUtil.readSequenceLength;
import static oracle.kv.impl.util.SerializationUtil.writeArrayLength;
import static oracle.kv.impl.util.SerializationUtil.writeFastExternalOrNull;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashSet;
import java.util.Set;

import oracle.kv.Consistency;
import oracle.kv.Durability;
import oracle.kv.FaultException;
import oracle.kv.impl.api.ops.InternalOperation;
import oracle.kv.impl.api.ops.NOP;
import oracle.kv.impl.fault.OperationFaultException;
import oracle.kv.impl.fault.TTLFaultException;
import oracle.kv.impl.security.AuthContext;
import oracle.kv.impl.topo.DatacenterId;
import oracle.kv.impl.topo.PartitionId;
import oracle.kv.impl.topo.RepGroupId;
import oracle.kv.impl.topo.RepNodeId;
import oracle.kv.impl.topo.ResourceId;
import oracle.kv.impl.topo.Topology;
import oracle.kv.impl.topo.change.TopologyChange;
import oracle.kv.impl.util.FastExternalizable;
import oracle.kv.impl.util.SerialVersion;
import oracle.kv.impl.util.SerializationUtil;
import oracle.kv.impl.util.contextlogger.LogContext;

/**
 * A request is issued either between Rep Nodes in the KV Store or from a
 * client to a Rep Node.  The request carries an operation to be performed.
 *
 * @see #writeFastExternal FastExternalizable format
 */
public class Request implements Externalizable, FastExternalizable {

    private static final long serialVersionUID = 1;

    /**
     * Used during testing: A non-zero value specifies the current serial
     * version, which acts as the maximum value for a request's serial version.
     */
    private static volatile short testCurrentSerialVersion;

    /**
     * Used during testing: A non-zero value specifies the minimum permitted
     * serial version accepted by the server.
     */
    public static volatile short testServerMinimumSerialVersion;

    /**
     * The serialization version of the request and all its sub-objects.
     */
    private short serialVersion;

    /**
     * The API operation to execute.
     */
    private InternalOperation op;

    /**
     * The partition ID is used to determine and validate the destination rep
     * group.
     */
    private PartitionId partitionId;

    /**
     * The replication group ID is used to determine and validate the
     * destination. If equal to RepGroupId.NULL_ID the partitionId is used.
     *
     * Introduced in SerialVersion.V4.
     */
    private RepGroupId repGroupId;

    /**
     * Whether the request performs write, versus read, operations.
     */
    private boolean write;

    /**
     * The durability of a write request, null for a read request.
     */
    private Durability durability;

    /**
     * The consistency of a read request, null for a write request.
     */
    private Consistency consistency;

    /**
     * The sequence number of the topology {@link Topology#getSequenceNumber}
     * used as the basis for routing the request.
     */
    private int topoSeqNumber;

    /**
     * Identifies the original request dispatcher making the request. It's not
     * updated through forwards of the request.
     */
    private ResourceId initialDispatcherId;

    /**
     * The "time to live" associated with the request. It's decremented each
     * time the message is forwarded. Messages may be forwarded across rep
     * groups (in case of obsolete partition information) and within a rep
     * group in the absence of current "master" information.
     */
    private int ttl;

    /**
     * The RNS through which the request was forwarded in an attempt to locate
     * a master. The number of such forwards cannot exceed the size of an RG.
     * Each element in the array contains the "group relative" nodeNum of the
     * RepNodeId. This array is used to ensure that there are no forwarding
     * loops within an RG.
     */
    private byte[] forwardingRNs = new byte[0];

    /**
     * The timeout associated with the request. It bounds the maximum time
     * taken to execute the request and does not include request transmission
     * times.
     */
    private int timeoutMs;

    /**
     * The AuthContext associated with the request
     */
    private AuthContext authCtx;

    /**
     * The IDs of the zones that can be used for reads, or null if not
     * restricted.  Introduced with {@link SerialVersion#V4}.
     */
    private int[] readZoneIds = null;

    /**
     * The LogContext associated with this request, or null if there is none.
     * Introduced with {@link SerialVersion#V16}
     */
    private LogContext lc = null;

    /**
     * Creates a partition based request. The target of this request is
     * specified by the partition ID.
     * 

* The topoSeqNumber param is used to determine the {@link TopologyChange * topology changes}, if any, that must be returned back in the response. *

* The remoteRequestHandlerId is used to determine the rep group state * changes, if any, that need to be returned back as part of the response. *

* @param op the operation to be performed * @param partitionId the target partition * @param topoSeqNumber identifies the topology used as the basis for the * request * @param dispatcherId identifies the invoking * {@link RequestDispatcher} * @param timeoutMs the maximum number of milliseconds that should be used * to execute the request * @param readZoneIds the IDs of the zones that can be used for reads, or * {@code null} for writes or unrestricted read requests */ public Request(InternalOperation op, PartitionId partitionId, boolean write, Durability durability, Consistency consistency, int ttl, int topoSeqNumber, ResourceId dispatcherId, int timeoutMs, int[] readZoneIds) { this(op, partitionId, RepGroupId.NULL_ID, write, durability, consistency, ttl, topoSeqNumber, dispatcherId, timeoutMs, readZoneIds, null); } /** * Same as above, with addition of LogContext argument. * * @param op * @param partitionId * @param write * @param durability * @param consistency * @param ttl * @param topoSeqNumber * @param dispatcherId * @param timeoutMs * @param readZoneIds * @param lc {@link LogContext} */ public Request(InternalOperation op, PartitionId partitionId, boolean write, Durability durability, Consistency consistency, int ttl, int topoSeqNumber, ResourceId dispatcherId, int timeoutMs, int[] readZoneIds, LogContext lc) { this(op, partitionId, RepGroupId.NULL_ID, write, durability, consistency, ttl, topoSeqNumber, dispatcherId, timeoutMs, readZoneIds, lc); } /** * Creates a group based request. The target of this request is specified * by the replication group ID. *

* The topoSeqNumber param is used to determine the {@link TopologyChange * topology changes}, if any, that must be returned back in the response. *

* The remoteRequestHandlerId is used to determine the rep group state * changes, if any, that need to be returned back as part of the response. *

* @param op the operation to be performed * @param repGroupId the target rep group * @param topoSeqNumber identifies the topology used as the basis for the * request * @param dispatcherId identifies the invoking {@link RequestDispatcher} */ public Request(InternalOperation op, RepGroupId repGroupId, boolean write, Durability durability, Consistency consistency, int ttl, int topoSeqNumber, ResourceId dispatcherId, int timeoutMs, int[] readZoneIds) { this(op, PartitionId.NULL_ID, repGroupId, write, durability, consistency, ttl, topoSeqNumber, dispatcherId, timeoutMs, readZoneIds, null); } /** * Same as above, with addition of LogContext argument. * * @param op * @param repGroupId * @param write * @param durability * @param consistency * @param ttl * @param topoSeqNumber * @param dispatcherId * @param timeoutMs * @param readZoneIds * @param lc */ public Request(InternalOperation op, RepGroupId repGroupId, boolean write, Durability durability, Consistency consistency, int ttl, int topoSeqNumber, ResourceId dispatcherId, int timeoutMs, int[] readZoneIds, LogContext lc) { this(op, PartitionId.NULL_ID, repGroupId, write, durability, consistency, ttl, topoSeqNumber, dispatcherId, timeoutMs, readZoneIds, lc); } private Request(InternalOperation op, PartitionId partitionId, RepGroupId repGroupId, boolean write, Durability durability, Consistency consistency, int ttl, int topoSeqNumber, ResourceId dispatcherId, int timeoutMs, int[] readZoneIds, LogContext lc) { checkNull("op", op); checkNull("partitionId", partitionId); checkNull("repGroupId", repGroupId); if (write) { checkNull("durability", durability); if (consistency != null) { throw new IllegalArgumentException( "Consistency should be null"); } if (readZoneIds != null) { throw new IllegalArgumentException( "ReadZoneIds should be null"); } } else { checkNull("consistency", consistency); if (durability != null) { throw new IllegalArgumentException( "Durability should be null"); } } checkNull("dispatcherId", dispatcherId); this.serialVersion = SerialVersion.UNKNOWN; this.op = op; this.partitionId = partitionId; this.repGroupId = repGroupId; this.write = write; this.durability = durability; this.consistency = consistency; this.ttl = ttl; this.topoSeqNumber = topoSeqNumber; this.initialDispatcherId = dispatcherId; this.timeoutMs = timeoutMs; this.readZoneIds = readZoneIds; this.lc = lc; } /** * Creates an instance from data in the specified input stream. * * @param in the input stream * @throws IOException if there is a problem reading from the input stream */ public Request(DataInput in) throws IOException { readExternalDataInput(in); } /** * Factory method for creating NOP requests. */ public static Request createNOP(int topoSeqNumber, ResourceId dispatcherId, int timeoutMs) { return new Request(new NOP(), PartitionId.NULL_ID, false, null, Consistency.NONE_REQUIRED, 1, topoSeqNumber, dispatcherId, timeoutMs, /* * NOP requests are not generated by users, so they * ignore read zones restrictions. * * TODO: Consider restricting NOP requests from * being sent to RNs outside of the specified read * zones. */ null); } /* for Externalizable */ public Request() { } @Override public void readExternal(ObjectInput in) throws IOException { readExternalDataInput(in); } private void readExternalDataInput(DataInput in) throws IOException { serialVersion = in.readShort(); final short minimumVersion = (testServerMinimumSerialVersion > 0) ? testServerMinimumSerialVersion : SerialVersion.MINIMUM; if (serialVersion < minimumVersion) { throw SerialVersion.clientUnsupportedException( serialVersion, minimumVersion); } partitionId = new PartitionId(in.readInt()); /* * A pre-V4 request will not have a group ID. In this case set it * to a null ID. */ repGroupId = (serialVersion < SerialVersion.V4) ? RepGroupId.NULL_ID : new RepGroupId(in.readInt()); if (!repGroupId.isNull() && !partitionId.isNull()) { throw new IllegalStateException("Both partition ID and group ID " + "are non-null"); } write = in.readBoolean(); if (write) { durability = Durability.readFastExternal(in, serialVersion); consistency = null; } else { durability = null; consistency = Consistency.readFastExternal(in, serialVersion); } ttl = in.readInt(); final byte asize = in.readByte(); forwardingRNs = new byte[asize]; for (int i = 0; i < asize; i++) { forwardingRNs[i] = in.readByte(); } timeoutMs = in.readInt(); topoSeqNumber = in.readInt(); initialDispatcherId = ResourceId.readFastExternal(in, serialVersion); op = InternalOperation.readFastExternal(in, serialVersion); /* * For V4 and later, read the number of restricted read zones followed * by that number of zone IDs, with 0 or -1 meaning no restriction. * Also, write the AuthContext object information. */ if (serialVersion >= SerialVersion.V4) { final int len = (serialVersion >= STD_UTF8_VERSION) ? readSequenceLength(in) : in.readInt(); if (len <= 0) { readZoneIds = null; } else { readZoneIds = new int[len]; for (int i = 0; i < len; i++) { readZoneIds[i] = in.readInt(); } } if (in.readBoolean()) { authCtx = new AuthContext(in, serialVersion); } else { authCtx = null; } } if (serialVersion >= SerialVersion.LOGCONTEXT_REQUEST_VERSION) { if (in.readBoolean()) { lc = new LogContext(in, serialVersion); } else { lc = null; } } } @Override public void writeExternal(ObjectOutput out) throws IOException { writeExternalDataOutput(out); } /** * Writes this object to the output stream. Format for {@code * serialVersion} {@link SerialVersion#STD_UTF8_VERSION} or greater: *

    *
  1. ({@link DataOutput#writeShort short}) {@link #getSerialVersion * serialVersion} *
  2. ({@link DataOutput#writeInt int}) {@link #getPartitionId * partitionId} *
  3. ({@link DataOutput#writeInt int}) {@link #getRepGroupId repGroupId} *
  4. ({@link DataOutput#writeBoolean boolean}) {@link #isWrite write} *
  5. [Choice] ({@link Durability}) {@link #getDurability * durability} // if write *
  6. [Choice] ({@link Consistency}) {@link #getConsistency * consistency} // if not write *
  7. ({@link DataOutput#writeInt int}) {@link #getTTL ttl} *
  8. ({@code byte}) number of forwarding RNs *
  9. ({@code byte[]}) {@link #getForwardingRNs forwardingRNs} *
  10. ({@link DataOutput#writeInt int}) {@link #getTimeout timeoutMs} *
  11. ({@link DataOutput#writeInt int}) {@link #getTopoSeqNumber * topoSeqNumber} *
  12. ({@link ResourceId}) {@link #getInitialDispatcherId * initialDispatcherId} *
  13. ({@link InternalOperation}) {@link #getOperation op} *
  14. ({@link SerializationUtil#writeArrayLength sequence length}) * number of read zones *
  15. ({@link DataOutput#writeInt int} {@code []}) {@link #getReadZoneIds * readZoneIds} *
  16. ({@link SerializationUtil#writeFastExternalOrNull AuthContext or * null}) {@link #getAuthContext authCtx} *
  17. ({@link SerializationUtil#writeFastExternalOrNull LogContext or null}) * {@link #getLogContext lc} // for {@code * serialVersion} {@link SerialVersion#LOGCONTEXT_REQUEST_VERSION} or * greater *
*/ @Override public void writeFastExternal(DataOutput out, @SuppressWarnings("hiding") short serialVersion) throws IOException { writeExternalDataOutput(out); } private void writeExternalDataOutput(DataOutput out) throws IOException { assert serialVersion != SerialVersion.UNKNOWN; /* * Verify that the server can handle this operation. */ short requiredVersion = op.getOpCode().requiredVersion(); if (requiredVersion > serialVersion) { throw new UnsupportedOperationException ("Attempting an operation that is not supported by " + "the server version. Server version is " + serialVersion + ", required version is " + requiredVersion + ", operation is " + op); } out.writeShort(serialVersion); out.writeInt(partitionId.getPartitionId()); /* The group ID was introduced in V4 */ if (serialVersion >= SerialVersion.V4) { out.writeInt(repGroupId.getGroupId()); } else { if (!repGroupId.isNull()) { throw new IllegalStateException("Attempting to write a newer " + "request with unsupported " + " serial version: " + serialVersion); } } out.writeBoolean(write); if (write) { durability.writeFastExternal(out, serialVersion); } else { consistency.writeFastExternal(out, serialVersion); } out.writeInt(ttl); if (forwardingRNs.length > Byte.MAX_VALUE) { throw new IllegalStateException( "Too many forwarding RNs: " + forwardingRNs.length); } out.writeByte(forwardingRNs.length); for (byte forwardingRN : forwardingRNs) { out.writeByte(forwardingRN); } out.writeInt(timeoutMs); out.writeInt(topoSeqNumber); initialDispatcherId.writeFastExternal(out, serialVersion); op.writeFastExternal(out, serialVersion); if (serialVersion >= SerialVersion.V4) { /* * For V4 and later, write the number of restricted read zones * followed by that number of zone IDs, with 0 meaning no * restriction */ if (serialVersion >= STD_UTF8_VERSION) { writeArrayLength(out, readZoneIds); } else { out.writeInt((readZoneIds != null) ? readZoneIds.length : 0); } if (readZoneIds != null) { for (final int znId : readZoneIds) { out.writeInt(znId); } } /* * for V4 and later, write AuthContext information */ writeFastExternalOrNull(out, serialVersion, authCtx); } else if (readZoneIds != null) { throw new OperationFaultException( "The store configuration specifies read zones, but read" + " zones are not supported by the target replication node"); } if (serialVersion >= SerialVersion.LOGCONTEXT_REQUEST_VERSION) { writeFastExternalOrNull(out, serialVersion, lc); } } public InternalOperation getOperation() { return op; } /** * Indicates if the request must be performed on the master. * * @return true if the request must be run on the master, false otherwise */ public boolean needsMaster() { return isWrite() || (getConsistency() == Consistency.ABSOLUTE); } /** * Indicates if the request must be performed on a replica rather * than the master. * * @return true if the request must be run on a replica, false otherwise */ @SuppressWarnings("deprecation") public boolean needsReplica() { return !isWrite() && (getConsistency() == Consistency.NONE_REQUIRED_NO_MASTER); } /** * Returns True if the request writes to the environment. */ public boolean isWrite() { return write; } /** * Returns the durability associated with a write request, or null for a * read request. */ public Durability getDurability() { return durability; } /** * Returns the consistency associated with a read request, or null for a * write request. */ public Consistency getConsistency() { return consistency; } /** * Must be called before serializing the request by passing it to a remote * method. */ public void setSerialVersion(short serialVersion) { /* Limit the serial version to testCurrentSerialVersion, if set */ if ((testCurrentSerialVersion != 0) && (serialVersion > testCurrentSerialVersion)) { serialVersion = testCurrentSerialVersion; } this.serialVersion = serialVersion; } /** * Set the current serial version to a different value, for testing. * Specifying {@code 0} reverts to the standard value. */ public static void setTestSerialVersion(final short testSerialVersion) { testCurrentSerialVersion = testSerialVersion; } /** * Returns a value used to construct a matching response for this request, * so that the client receives a response serialized with the same version * as the request. */ short getSerialVersion() { return serialVersion; } /** * Returns the partition ID associated with the request. */ public PartitionId getPartitionId() { return partitionId; } public void setPartitionId(PartitionId partitionId) { this.partitionId = partitionId; } public RepGroupId getRepGroupId() { return repGroupId; } public int getTopoSeqNumber() { return topoSeqNumber; } public void setTopoSeqNumber(int topoSeqNumber) { this.topoSeqNumber = topoSeqNumber; } public ResourceId getInitialDispatcherId() { return initialDispatcherId; } public int getTTL() { return ttl; } public void decTTL() throws FaultException { if (ttl-- == 0) { throw new TTLFaultException ("TTL exceeded for request: " + getOperation() + " request dispatched by: " + getInitialDispatcherId()); } } public int getTimeout() { return timeoutMs; } public void setTimeout(int timeoutMs) { this.timeoutMs = timeoutMs; } @Override public String toString() { return op.toString(); } /** * Clears out the RNs in the forwarding chain. This typically done before * the request is forwarded to a node in a different group. */ public void clearForwardingRNs() { forwardingRNs = new byte[0]; } /** * Returns the set of RNIds that forwarded this request. */ public Set getForwardingRNs(int rgid) { final HashSet forwardingRNIds = new HashSet(); for (int nodeNum : forwardingRNs) { forwardingRNIds.add(new RepNodeId(rgid, nodeNum)); } return forwardingRNIds; } /** * Updates the list of forwarding RNs, if the current dispatcher is an RN. */ public void updateForwardingRNs(ResourceId currentDispatcherId, int groupId) { if (!currentDispatcherId.getType().isRepNode()) { return; } assert currentDispatcherId.getType().isRepNode(); final RepNodeId repNodeId = (RepNodeId) currentDispatcherId; if (repNodeId.getGroupId() == groupId) { /* Add this RN to the list. */ final byte[] updateList = new byte[forwardingRNs.length + 1]; if (updateList.length > Byte.MAX_VALUE) { throw new IllegalStateException( "Too many forwarding RNs: " + updateList.length); } System.arraycopy(forwardingRNs, 0, updateList, 0, forwardingRNs.length); if (repNodeId.getNodeNum() > Byte.MAX_VALUE) { throw new IllegalStateException( "Invalid forwarding RN ID: " + repNodeId.getNodeNum()); } updateList[forwardingRNs.length] = (byte) repNodeId.getNodeNum(); forwardingRNs = updateList; } else { /* Forwarding outside the group. */ forwardingRNs = new byte[0]; } } /** * Returns true if the request was initiated by the * resourceId. */ public boolean isInitiatingDispatcher(ResourceId resourceId) { return initialDispatcherId.equals(resourceId); } /** * Set the security context in preparation for execution. */ public void setAuthContext(AuthContext useAuthCtx) { this.authCtx = useAuthCtx; } /** * Get the security context */ public AuthContext getAuthContext() { return authCtx; } /** * Get the log context */ public LogContext getLogContext() { return lc; } /** * Returns the IDs of the zones that can be used for read operations, or * {@code null} if not restricted. * * @return the zone IDs or {@code null} */ public int[] getReadZoneIds() { return readZoneIds; } /** * Checks if an RN in a zone with the specified zone ID can be used for * this request. * * @param znId the zone ID or {@code null} if not known * @return whether an RN in the specified zone can be used */ public boolean isPermittedZone(final DatacenterId znId) { if (write || (readZoneIds == null)) { return true; } if (znId == null) { return false; } final int znIdInt = znId.getDatacenterId(); for (int elem : readZoneIds) { if (elem == znIdInt) { return true; } } return false; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy