/*
 * Copyright (c) 2017, 2018 Nicira, Inc.
 *
 * 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.
 */

#ifndef RAFT_RPC_H
#define RAFT_RPC_H 1

/* Data structures used internally by Raft implementation for JSON-RPC. */

#include <stdbool.h>
#include <stdint.h>
#include "openvswitch/uuid.h"
#include "raft.h"
#include "raft-private.h"
#include "sset.h"

struct ds;

#define RAFT_RPC_TYPES                                                  \
    /* Hello RPC. */                                                    \
    RAFT_RPC(RAFT_RPC_HELLO_REQUEST, hello_request)                     \
                                                                        \
    /* AppendEntries RPC. */                                            \
    RAFT_RPC(RAFT_RPC_APPEND_REQUEST, append_request)                   \
    RAFT_RPC(RAFT_RPC_APPEND_REPLY, append_reply)                       \
                                                                        \
    /* RequestVote RPC. */                                              \
    RAFT_RPC(RAFT_RPC_VOTE_REQUEST, vote_request)                       \
    RAFT_RPC(RAFT_RPC_VOTE_REPLY, vote_reply)                           \
                                                                        \
    /* AddServer RPC. */                                                \
    RAFT_RPC(RAFT_RPC_ADD_SERVER_REQUEST, add_server_request)           \
    RAFT_RPC(RAFT_RPC_ADD_SERVER_REPLY, add_server_reply)               \
                                                                        \
    /* RemoveServer RPC. */                                             \
    RAFT_RPC(RAFT_RPC_REMOVE_SERVER_REQUEST, remove_server_request)     \
    RAFT_RPC(RAFT_RPC_REMOVE_SERVER_REPLY, remove_server_reply)         \
                                                                        \
    /* InstallSnapshot RPC. */                                          \
    RAFT_RPC(RAFT_RPC_INSTALL_SNAPSHOT_REQUEST, install_snapshot_request) \
    RAFT_RPC(RAFT_RPC_INSTALL_SNAPSHOT_REPLY, install_snapshot_reply)   \
                                                                        \
    /* BecomeLeader RPC. */                                             \
    RAFT_RPC(RAFT_RPC_BECOME_LEADER, become_leader)                     \
                                                                        \
    /* ExecuteCommand RPC. */                                           \
    RAFT_RPC(RAFT_RPC_EXECUTE_COMMAND_REQUEST, execute_command_request) \
    RAFT_RPC(RAFT_RPC_EXECUTE_COMMAND_REPLY, execute_command_reply)

enum raft_rpc_type {
#define RAFT_RPC(ENUM, NAME) ENUM,
    RAFT_RPC_TYPES
#undef RAFT_RPC
};

const char *raft_rpc_type_to_string(enum raft_rpc_type);
bool raft_rpc_type_from_string(const char *, enum raft_rpc_type *);

struct raft_rpc_common {
    enum raft_rpc_type type;    /* Message type. */
    struct uuid sid;            /* Peer server (source or destination). */
    char *comment;              /* Human-friendly additional text. */
};

struct raft_hello_request {
    struct raft_rpc_common common;
    char *address;              /* Sender's address. */
};

struct raft_append_request {
    struct raft_rpc_common common;
    uint64_t term;              /* Leader's term. */
    uint64_t prev_log_index;    /* Log entry just before new ones. */
    uint64_t prev_log_term;     /* Term of prev_log_index entry. */
    uint64_t leader_commit;     /* Leader's commit_index. */

    /* The append request includes 0 or more log entries.  entries[0] is for
     * log entry 'prev_log_index + 1', and so on.
     *
     * A heartbeat append_request has no terms. */
    struct raft_entry *entries;
    unsigned int n_entries;
};

enum raft_append_result {
    RAFT_APPEND_OK,             /* Success. */
    RAFT_APPEND_INCONSISTENCY,  /* Failure due to log inconsistency. */
    RAFT_APPEND_IO_ERROR,       /* Failure due to I/O error. */
};

const char *raft_append_result_to_string(enum raft_append_result);
bool raft_append_result_from_string(const char *, enum raft_append_result *);

struct raft_append_reply {
    struct raft_rpc_common common;

    /* Copied from the state machine of the reply's sender. */
    uint64_t term;             /* Current term, for leader to update itself. */
    uint64_t log_end;          /* To allow capping next_index, see 4.2.1. */

    /* Copied from request. */
    uint64_t prev_log_index;   /* Log entry just before new ones. */
    uint64_t prev_log_term;    /* Term of prev_log_index entry. */
    unsigned int n_entries;

    /* Result. */
    enum raft_append_result result;
};

struct raft_vote_request {
    struct raft_rpc_common common;
    uint64_t term;           /* Candidate's term. */
    uint64_t last_log_index; /* Index of candidate's last log entry. */
    uint64_t last_log_term;  /* Term of candidate's last log entry. */
    bool leadership_transfer;  /* True to override minimum election timeout. */
    bool is_prevote;         /* True: pre-vote; False: real vote (default). */
};

struct raft_vote_reply {
    struct raft_rpc_common common;
    uint64_t term;          /* Current term, for candidate to update itself. */
    struct uuid vote;       /* Server ID of vote. */
    bool is_prevote;        /* Copy of the is_prevote from the request,
                             * primarily for validation. */
};

struct raft_add_server_request {
    struct raft_rpc_common common;
    char *address;              /* Address of new server. */
};

struct raft_remove_server_request {
    struct raft_rpc_common common;
    struct uuid sid;            /* Server to remove. */

    /* Nonnull if request was received via unixctl. */
    struct unixctl_conn *requester_conn;
};

/* The operation committed and is now complete. */
#define RAFT_SERVER_COMPLETED "completed"

/* The operation could not be initiated because this server is not the current
 * leader.  Only the leader can add or remove servers. */
#define RAFT_SERVER_NOT_LEADER "not leader"

/* An operation to add a server succeeded without any change because the server
 * was already part of the cluster. */
#define RAFT_SERVER_ALREADY_PRESENT "already in cluster"

/* An operation to remove a server succeeded without any change because the
 * server was not part of the cluster. */
#define RAFT_SERVER_ALREADY_GONE "already not in cluster"

/* The operation could not be initiated because an identical
 * operation was already in progress. */
#define RAFT_SERVER_IN_PROGRESS "in progress"

/* Adding a server failed because of a timeout.  This could mean that the
 * server was entirely unreachable, or that it became unreachable partway
 * through populating it with an initial copy of the log.  In the latter case,
 * retrying the operation should resume where it left off. */
#define RAFT_SERVER_TIMEOUT "timeout"

/* The operation was initiated but it later failed because this server lost
 * cluster leadership.  The operation may be retried against the new cluster
 * leader.  For adding a server, if the log was already partially copied to the
 * new server, retrying the operation should resume where it left off. */
#define RAFT_SERVER_LOST_LEADERSHIP "lost leadership"

/* Adding a server was canceled by submission of an operation to remove the
 * same server, or removing a server was canceled by submission of an operation
 * to add the same server. */
#define RAFT_SERVER_CANCELED "canceled"

/* Adding or removing a server could not be initiated because the operation to
 * remove or add the server, respectively, has been logged but not committed.
 * The new operation may be retried once the former operation commits. */
#define RAFT_SERVER_COMMITTING "committing"

/* Adding or removing a server was canceled because the leader shut down. */
#define RAFT_SERVER_SHUTDOWN "shutdown"

/* Removing a server could not be initiated because, taken together with any
 * other scheduled server removals, the cluster would be empty.  (This
 * calculation ignores scheduled or uncommitted add server operations because
 * of the possibility that they could fail.)  */
#define RAFT_SERVER_EMPTY "empty"

struct raft_add_server_reply {
    struct raft_rpc_common common;
    bool success;
    struct sset remote_addresses;
};

struct raft_remove_server_reply {
    struct raft_rpc_common common;
    bool success;

    /* SID of the removed server, but all-zeros if it is the same as the
     * destination of the RPC.  (Older ovsdb-server did not have 'target_sid'
     * and assumed that the destination was always the target, so by omitting
     * 'target_sid' when this is the case we can preserve a small amount of
     * inter-version compatibility.) */
    struct uuid target_sid;
};

struct raft_install_snapshot_request {
    struct raft_rpc_common common;

    uint64_t term;              /* Leader's term. */

    uint64_t last_index;        /* Covers everything up & including this. */
    uint64_t last_term;         /* Term of last_index. */
    struct uuid last_eid;       /* Last entry ID. */
    struct json *last_servers;
    uint64_t election_timer;

    /* Data. */
    struct json *data;
};

struct raft_install_snapshot_reply {
    struct raft_rpc_common common;

    uint64_t term;              /* For leader to update itself. */

    /* Repeated from the install_snapshot request. */
    uint64_t last_index;
    uint64_t last_term;
};

struct raft_become_leader {
    struct raft_rpc_common common;

    uint64_t term;              /* Leader's term. */
};

struct raft_execute_command_request {
    struct raft_rpc_common common;

    struct json *data;
    struct uuid prereq;
    struct uuid result;
};

struct raft_execute_command_reply {
    struct raft_rpc_common common;

    struct uuid result;
    enum raft_command_status status;
    uint64_t commit_index;
};

union raft_rpc {
    enum raft_rpc_type type;
    struct raft_rpc_common common;
#define RAFT_RPC(ENUM, NAME) struct raft_##NAME NAME;
    RAFT_RPC_TYPES
#undef RAFT_RPC
};

#define RAFT_RPC(ENUM, NAME)                        \
    static inline const struct raft_##NAME *        \
    raft_##NAME##_cast(const union raft_rpc *rpc)   \
    {                                               \
        ovs_assert(rpc->type == ENUM);              \
        return &rpc->NAME;                          \
    }
RAFT_RPC_TYPES
#undef RAFT_RPC

void raft_rpc_uninit(union raft_rpc *);
union raft_rpc *raft_rpc_clone(const union raft_rpc *);

struct jsonrpc_msg *raft_rpc_to_jsonrpc(const struct uuid *cid,
                                        const struct uuid *sid,
                                        const union raft_rpc *);
struct ovsdb_error *raft_rpc_from_jsonrpc(struct uuid *cid,
                                          const struct uuid *sid,
                                          const struct jsonrpc_msg *,
                                          union raft_rpc *)
    OVS_WARN_UNUSED_RESULT;

void raft_rpc_format(const union raft_rpc *, struct ds *);

uint64_t raft_rpc_get_term(const union raft_rpc *);
const struct uuid *raft_rpc_get_vote(const union raft_rpc *);
uint64_t raft_rpc_get_min_sync_index(const union raft_rpc *);

#endif /* lib/raft-rpc.h */
