/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/**
 * These .proto interfaces are private and unstable.
 * Please see http://wiki.apache.org/hadoop/Compatibility
 * for what changes are allowed for a *unstable* .proto interface.
 */
syntax = "proto2";

option java_package = "org.apache.hadoop.hdds.protocol.proto";

option java_outer_classname = "StorageContainerDatanodeProtocolProtos";

option java_generic_services = true;

option java_generate_equals_and_hash = true;

package hadoop.hdds;

import "hdds.proto";


message SCMDatanodeRequest {
  required Type cmdType = 1; // Type of the command

  optional string traceID = 2;

  optional SCMVersionRequestProto getVersionRequest = 3;
  optional SCMRegisterRequestProto registerRequest = 4;
  optional SCMHeartbeatRequestProto sendHeartbeatRequest = 5;
}

message SCMDatanodeResponse {
  required Type cmdType = 1; // Type of the command

  optional string traceID = 2;

  optional bool success = 3 [default = true];

  optional string message = 4;

  required Status status = 5;

  optional SCMVersionResponseProto getVersionResponse = 6;
  optional SCMRegisteredResponseProto registerResponse = 7;
  optional SCMHeartbeatResponseProto sendHeartbeatResponse = 8;

}

enum Type {
  GetVersion = 1;
  Register = 2;
  SendHeartbeat = 3;
}

enum Status {
  OK = 1;
  ERROR = 2;
}

/**
 * DataNode will advertise its current layout version through
 * heartbeat as well as registration mechanism.
 */
message LayoutVersionProto {
   required uint32 metadataLayoutVersion = 1;
   required uint32 softwareLayoutVersion = 2;
}

/**
 * Request for version info of the software stack on the server.
 */
message SCMVersionRequestProto {}

/**
* Generic response that is send to a version request. This allows keys to be
* added on the fly and protocol to remain stable.
*/
message SCMVersionResponseProto {
  required uint32 softwareVersion = 1;
  repeated hadoop.hdds.KeyValue keys = 2;
}

message SCMRegisterRequestProto {
  required ExtendedDatanodeDetailsProto extendedDatanodeDetails = 1;
  required NodeReportProto nodeReport = 2;
  required ContainerReportsProto containerReport = 3;
  required PipelineReportsProto pipelineReports = 4;
  optional LayoutVersionProto dataNodeLayoutVersion = 5;
}

/**
 * Datanode ID returned by the SCM. This is similar to name node
 * registeration of a datanode.
 */
message SCMRegisteredResponseProto {
  enum ErrorCode {
    success = 1;
    errorNodeNotPermitted = 2;
  }
  required ErrorCode errorCode = 1;
  required string datanodeUUID = 2;
  required string clusterID = 3;
  optional SCMNodeAddressList addressList = 4;
  optional string hostname = 5;
  optional string ipAddress = 6;
  optional string networkName = 7;
  optional string networkLocation = 8;
}

/**
* This message is send by data node to indicate that it is alive or it is
* registering with the node manager.
*/
message SCMHeartbeatRequestProto {
  required DatanodeDetailsProto datanodeDetails = 1;
  optional NodeReportProto nodeReport = 2;
  optional ContainerReportsProto containerReport = 3;
  repeated IncrementalContainerReportProto incrementalContainerReport = 4;
  repeated CommandStatusReportsProto commandStatusReports = 5;
  optional ContainerActionsProto containerActions = 6;
  optional PipelineActionsProto pipelineActions = 7;
  optional PipelineReportsProto pipelineReports = 8;
  optional LayoutVersionProto dataNodeLayoutVersion = 9;
  optional CommandQueueReportProto commandQueueReport = 10;
}

message CommandQueueReportProto {
  repeated SCMCommandProto.Type command = 1;
  repeated uint32 count = 2;
}

/*
 * A group of commands for the datanode to execute
 */
message SCMHeartbeatResponseProto {
  // TODO: change this to DatanodeID
  required string datanodeUUID = 1;
  repeated SCMCommandProto commands = 2;

  // Same as term in SCMCommandProto
  optional int64 term = 3;
}

message SCMNodeAddressList {
  repeated string addressList = 1;
}

/**
* This message is send along with the heart beat to report datanode
* storage utilization to SCM.
*/
message NodeReportProto {
  repeated StorageReportProto storageReport = 1;
  repeated MetadataStorageReportProto metadataStorageReport = 2;
  repeated StorageReportProto dbStorageReport = 3;
}

message StorageReportProto {
  required string storageUuid = 1;
  required string storageLocation = 2;
  optional uint64 capacity = 3 [default = 0];
  optional uint64 scmUsed = 4 [default = 0];
  optional uint64 remaining = 5 [default = 0];
  optional StorageTypeProto storageType = 6 [default = DISK];
  optional bool failed = 7 [default = false];
  optional uint64 committed = 8 [default = 0];
  optional uint64 freeSpaceToSpare = 9 [default = 0];
  optional uint64 reserved = 10;
  /*
   Raw filesystem stats (as reported by the local filesystem). These represent the real device
   capacity/available, independent of Ozone's reserved-space adjustment.
   */
  optional uint64 fsCapacity = 11 [default = 0];
  optional uint64 fsAvailable = 12 [default = 0];
}

message MetadataStorageReportProto {
  required string storageLocation = 1;
  optional StorageTypeProto storageType = 2 [default = DISK];
  optional uint64 capacity = 3 [default = 0];
  optional uint64 scmUsed = 4 [default = 0];
  optional uint64 remaining = 5 [default = 0];
  optional bool failed = 6 [default = false];
}


message ContainerReportsProto {
  repeated ContainerReplicaProto reports = 1;
}

message IncrementalContainerReportProto {
  repeated ContainerReplicaProto report = 1;
}

message ContainerReplicaProto {
  enum State {
    OPEN = 1;
    CLOSING = 2;
    QUASI_CLOSED = 3;
    CLOSED = 4;
    UNHEALTHY = 5;
    INVALID = 6;
    DELETED = 7;
  }
  required int64 containerID = 1;
  required State state = 2;
  optional int64 size = 3;
  optional int64 used = 4;
  optional int64 keyCount = 5; // keyCount here refers to BlockCount
  optional int64 readCount = 6;
  optional int64 writeCount = 7;
  optional int64 readBytes = 8;
  optional int64 writeBytes = 9;
  optional string finalhash = 10 [ deprecated = true ];
  optional int64 deleteTransactionId = 11;
  optional uint64 blockCommitSequenceId = 12;
  optional string originNodeId = 13;
  optional int32 replicaIndex = 14;
  optional bool isEmpty = 15 [default = false];
  optional int64 dataChecksum = 16;
}

message CommandStatusReportsProto {
  repeated CommandStatus cmdStatus = 1;
}

message CommandStatus {
  enum Status {
    PENDING = 1;
    EXECUTED = 2;
    FAILED = 3;
  }
  required int64 cmdId = 1;
  required Status status = 2 [default = PENDING];
  required SCMCommandProto.Type type = 3;
  optional string msg = 4;
  optional ContainerBlocksDeletionACKProto blockDeletionAck = 5;
}

message ContainerActionsProto {
  repeated ContainerAction containerActions = 1;
}

message ContainerAction {
  enum Action {
    CLOSE = 1;
  }

  enum Reason {
    CONTAINER_FULL = 1;
    CONTAINER_UNHEALTHY = 2;
  }

  required int64 containerID = 1;
  required Action action = 2;
  optional Reason reason = 3;
}

message PipelineReport {
  required PipelineID pipelineID = 1;
  required bool isLeader = 2;
  optional uint64 bytesWritten = 3 [deprecated = true];
}

message PipelineReportsProto {
  repeated PipelineReport pipelineReport = 1;
}

message PipelineActionsProto {
  repeated PipelineAction pipelineActions = 1;
}

message ClosePipelineInfo {
  enum Reason {
    PIPELINE_FAILED = 1;
    PIPELINE_LOG_FAILED = 2;
    STATEMACHINE_TRANSACTION_FAILED = 3;
  }
  required PipelineID pipelineID = 1;
  optional Reason reason = 3;
  optional string detailedReason = 4;
}

message PipelineAction {
  enum Action {
    CLOSE = 1;
  }

  /**
   * Action will be used to identify the correct pipeline action.
   */
  required Action action = 1;
  optional ClosePipelineInfo closePipeline = 2;
}

/*
 * These are commands returned by SCM for to the datanode to execute.
 */
message SCMCommandProto {
  enum Type {
    unknownScmCommand = 0;
    reregisterCommand = 1;
    deleteBlocksCommand = 2;
    closeContainerCommand = 3;
    deleteContainerCommand = 4;
    replicateContainerCommand = 5;
    createPipelineCommand = 6;
    closePipelineCommand = 7;
    setNodeOperationalStateCommand = 8;
    finalizeNewLayoutVersionCommand = 9;
    refreshVolumeUsageInfo = 10;
    reconstructECContainersCommand = 11;
    reconcileContainerCommand = 12;
  }
  // TODO: once we start using protoc 3.x, refactor this message using "oneof"
  required Type commandType = 1;
  optional ReregisterCommandProto reregisterCommandProto = 2;
  optional DeleteBlocksCommandProto deleteBlocksCommandProto = 3;
  optional CloseContainerCommandProto closeContainerCommandProto = 4;
  optional DeleteContainerCommandProto deleteContainerCommandProto = 5;
  optional ReplicateContainerCommandProto replicateContainerCommandProto = 6;
  optional CreatePipelineCommandProto createPipelineCommandProto = 7;
  optional ClosePipelineCommandProto closePipelineCommandProto = 8;
  optional SetNodeOperationalStateCommandProto setNodeOperationalStateCommandProto = 9;
  optional FinalizeNewLayoutVersionCommandProto
  finalizeNewLayoutVersionCommandProto = 10;
  optional RefreshVolumeUsageCommandProto refreshVolumeUsageCommandProto = 11;
  optional ReconstructECContainersCommandProto reconstructECContainersCommandProto = 12;
  optional ReconcileContainerCommandProto reconcileContainerCommandProto = 13;

  // If running upon Ratis, holds term of underlying RaftServer iff current
  // SCM is a leader. If running without Ratis, holds SCMContext.INVALID_TERM.
  optional int64 term = 15;
  optional string encodedToken = 16;
  optional int64 deadlineMsSinceEpoch = 17;
}

/**
 * SCM informs a datanode to register itself again.
 * With receiving this command, datanode will transit to REGISTER state.
 */
message ReregisterCommandProto {}


// HB response from SCM, contains a list of block deletion transactions.
message DeleteBlocksCommandProto {
  repeated DeletedBlocksTransaction deletedBlocksTransactions = 1;
  required int64 cmdId = 3;
}

// The deleted blocks which are stored in deletedBlock.db of scm.
// We don't use BlockID because this only contains multiple localIDs
// of the same containerID.
message DeletedBlocksTransaction {
  required int64 txID = 1;
  required int64 containerID = 2;
  repeated int64 localID = 3;
  // the retry time of sending deleting command to datanode.
  // We don't have to store the retry count in DB.
  optional int32 count = 4 [deprecated=true];
  optional uint64 totalBlockSize = 5;
  optional uint64 totalBlockReplicatedSize = 6;
  optional uint64 totalSizePerReplica = 7;
}

// ACK message datanode sent to SCM, contains the result of
// block deletion transactions.
message ContainerBlocksDeletionACKProto {
  message DeleteBlockTransactionResult {
    required int64 txID = 1;
    required int64 containerID = 2;
    required bool success = 3;
  }
  repeated DeleteBlockTransactionResult results = 1;
  required string dnId = 2;
}

/**
This command asks the datanode to close a specific container.
*/
message CloseContainerCommandProto {
  required int64 containerID = 1;
  required PipelineID pipelineID = 2;
  // cmdId will be removed
  required int64 cmdId = 3;
  // Force will be used when closing a container out side of ratis.
  optional bool force = 4 [default = false];
}

/**
This command asks the datanode to delete a specific container.
*/
message DeleteContainerCommandProto {
  required int64 containerID = 1;
  required int64 cmdId = 2;
  required bool force = 3 [default = false];
  optional int32 replicaIndex = 4;
}

enum ReplicationCommandPriority {
  NORMAL = 1;
  LOW = 2;
}

/**
This command asks the datanode to replicate a container from specific sources.
*/
message ReplicateContainerCommandProto {
  required int64 containerID = 1;
  repeated DatanodeDetailsProto sources = 2;
  required int64 cmdId = 3;
  optional int32 replicaIndex = 4;
  optional DatanodeDetailsProto target = 5;
  optional ReplicationCommandPriority priority = 6 [default = NORMAL];
}

/**
* This command asks the datanode to reconstruct the missing EC containers by
* using remaining containers from sources.
*/
message ReconstructECContainersCommandProto {
  required int64 containerID = 1;
  repeated DatanodeDetailsAndReplicaIndexProto sources = 2;
  repeated DatanodeDetailsProto targets = 3;
  required bytes missingContainerIndexes = 4;
  required ECReplicationConfig ecReplicationConfig = 5;
  required int64 cmdId = 6;
}

message DatanodeDetailsAndReplicaIndexProto {
    required DatanodeDetailsProto datanodeDetails = 1;
    required int32 replicaIndex = 2;
}

/**
This command asks the datanode to create a pipeline.
*/
message CreatePipelineCommandProto {
  required PipelineID pipelineID = 1;
  required ReplicationType type = 2;
  required ReplicationFactor factor = 3;
  repeated DatanodeDetailsProto datanode = 4;
  required int64 cmdId = 5;
  repeated int32 priority = 6;
}

/**
This command asks the datanode to close a pipeline.
*/
message ClosePipelineCommandProto {
  required PipelineID pipelineID = 1;
  required int64 cmdId = 2;
}

/**
This command asks the datanode to refresh disk usage immediately.
*/
message RefreshVolumeUsageCommandProto {
  required int64 cmdId = 1;
}

message SetNodeOperationalStateCommandProto {
  required  int64 cmdId = 1;
  required  NodeOperationalState nodeOperationalState = 2;
  required  int64 stateExpiryEpochSeconds = 3;
}

message CRLStatusReport {
  required int64 receivedCrlId=1;
  repeated int64 pendingCrlIds=2;
}

/**
 * This command asks the datanode to process a new CRL.
 */
message ProcessCRLCommandProto {
  required CRLInfoProto crlInfo = 1;
}

/**
 * This command asks the DataNode to finalize a new layout version.
 */
message FinalizeNewLayoutVersionCommandProto {
  required bool finalizeNewLayoutVersion = 1 [default = false];
  required LayoutVersionProto dataNodeLayoutVersion = 2;
  required int64 cmdId = 3;
}

/**
This command asks the datanode to reconcile its copy of a container with its peer datanodes that also have a copy of
the container.
*/
message ReconcileContainerCommandProto {
  required int64 containerID = 1;
  repeated DatanodeDetailsProto peers = 2;
}

/**
 * Protocol used from a datanode to StorageContainerManager.
 *
 * Please see the request and response messages for details of the RPC calls.
 *
 * Here is a simple state diagram that shows how a datanode would boot up and
 * communicate with SCM.
 *
 *           -----------------------
 *          |         Start         |
 *           ---------- ------------
 *                     |
 *                     |
 *                     |
 *                     |
 *                     |
 *                     |
 *                     |
 *           ----------v-------------
 *          |   Searching for  SCM    ------------
 *           ---------- -------------             |
 *                     |                          |
 *                     |                          |
 *                     |                ----------v-------------
 *                     |               | Register if needed     |
 *                     |                ----------- ------------
 *                     |                           |
 *                     v                           |
 *            ----------- ----------------         |
 *  ---------   Heartbeat state           <--------
 * |          --------^-------------------
 * |                  |
 * |                  |
 * |                  |
 * |                  |
 * |                  |
 * |                  |
 * |                  |
 *  ------------------
 *
 *
 *
 * Here is how this protocol is used by the datanode. When a datanode boots up
 * it moves into a stated called SEARCHING_SCM. In this state datanode is
 * trying to establish communication with the SCM. The address of the SCMs are
 * retrieved from the configuration information.
 *
 * In the SEARCHING_SCM state, only rpc call made by datanode is a getVersion
 * call to SCM. Once any of the SCMs reply, datanode checks if it has a local
 * persisted datanode ID. If it has this means that this datanode is already
 * registered with some SCM. If this file is not found, datanode assumes that
 * it needs to do a registration.
 *
 * If registration is need datanode moves into REGISTER state. It will
 * send a register call with DatanodeDetailsProto data structure and persist
 * that info.
 *
 * The response to the command contains clusterID. This information is
 * also persisted by the datanode and moves into heartbeat state.
 *
 * Once in the heartbeat state, datanode sends heartbeats and container reports
 * to SCM and process commands issued by SCM until it is shutdown.
 *
 */
service StorageContainerDatanodeProtocolService {

  //Message sent from Datanode to SCM as a heartbeat.
  rpc submitRequest (SCMDatanodeRequest) returns (SCMDatanodeResponse);
}
