syntax = "proto3";

package docker.cluster.api;

import "timestamp/timestamp.proto"; // TODO(stevvooe): use our own until we fix gogoproto/deepcopy
import "gogoproto/gogo.proto";

// This file contains types that are common to objects and spec or that are not
// considered first-class within the cluster object-model.

// Version tracks the last time an object in the store was updated.
message Version {
	uint64 index = 1;
}

message Resources {
	// Amount of CPUs (e.g. 2000000000 = 2 CPU cores)
	int64 nano_cpus = 1 [(gogoproto.customname) = "NanoCPUs"];

	// Amount of memory in bytes.
	int64 memory_bytes = 2;
}

message ResourceRequirements {
	Resources limits = 1;
	Resources reservations = 2;
}

message Platform {
	// Architecture (e.g. x86_64)
	string architecture = 1;

	// Operating System (e.g. linux)
	string os = 2 [(gogoproto.customname) = "OS"];
}

message NodeDescription {
	// Hostname of the node as reported by the agent.
	// This is different from spec.meta.name which is user-defined.
	string hostname = 1;

	// Platform of the node.
	Platform platform = 2;

	// Total resources of the node.
	Resources resources = 3;
}

message MemberDescription {
	// Name of the node
	string name = 1;

	// Cluster the member belongs to
	string cluster = 2;
}

// RaftNode represents a raft node to be added or removed from
// an existing raft cluster
// TODO(stevvooe): Merge with MemberDescription.
message RaftNode {
	uint64 id = 1 [(gogoproto.customname) = "ID"];
	string addr = 2;
}

message MemberStatus {
	bool leader = 1;

	enum State {
		// Unknown indicates that the manager state cannot be resolved
		UNKNOWN = 0;

		// Unreachable indicates that the node cannot be contacted by other
		// raft cluster members.
		UNREACHABLE = 1;

		// Reachable indicates that the node is healthy and reachable
		// by other members.
		REACHABLE = 2;
	}

	State state = 2;
	string message = 3;
}

message NodeStatus {
	// TODO(aluzzardi) These should be using `gogoproto.enumvalue_customname`.
	enum State {
		// Unknown indicates the node state cannot be resolved.
		UNKNOWN = 0;

		// Down indicates the node is down.
		DOWN = 1;

		// Ready indicates the node is ready to accept tasks.
		READY = 2;

		// Disconnected indicates the node is currently trying to find new manager.
		DISCONNECTED = 3;
	}

	State state = 1;
	string message = 2;
}

message Image {
	// reference is a docker image reference. This can include a rpository, tag
	// or be fully qualified witha digest. The format is specified in the
	// distribution/reference package.
	string reference = 1;
}

message Mount {
	// Target path in container
	string target = 1;

	// Source path to mount
	string source = 2;

	// Mask
	enum Mask {
		option (gogoproto.goproto_enum_prefix) = false;
		option (gogoproto.enum_customname) = "Mask";

		READ_ONLY = 0 [(gogoproto.enumvalue_customname) = "MountMaskReadOnly"];
		READ_WRITE = 1 [(gogoproto.enumvalue_customname) = "MountMaskReadWrite"];
	}
	Mask mask = 3;

	enum Type {
		option (gogoproto.goproto_enum_prefix) = false;
		option (gogoproto.enum_customname) = "Type";

		BIND = 0 [(gogoproto.enumvalue_customname) = "MountTypeBind"]; // Bind mount host dir
		EPHEMERAL = 1 [(gogoproto.enumvalue_customname) = "MountTypeEphemeral"]; // Scratch dir
		VOLUME = 2 [(gogoproto.enumvalue_customname) = "MountTypeVolume"];  // Remote storage volumes
	}

	Type type = 4;

	// Source path to mount
	string volume_name = 5;
}

// Container specifies runtime parameters for a container.
message Container {
	// Resource requirements for the container.
	ResourceRequirements resources = 1;

	Image image = 2;

	// Command to run the the container. The first element is a path to the
	// executable and the following elements are treated as arguments.
	//
	// If command is empty, execution will fall back to the image's entrypoint.
	repeated string command = 3;

	// Args specifies arguments provided to the image's entrypoint.
	// Ignored if command is specified.
	repeated string args = 4;

	// Env specifies the environment variables for the container in NAME=VALUE
	// format. These must be compliant with  [IEEE Std
	// 1003.1-2001](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html).
	repeated string env = 5;

	// Dir defines the working directory to set for the container process.
	string dir = 6;

	// User specifies the user that should be employed to run the container.
	string user = 7;

	// NetworkAttachmentSpec describes a desired attachment to the named network or
	// a specific network_id.
	message NetworkAttachment {
		oneof reference {
			string name = 1;
			string network_id = 2 [(gogoproto.customname) = "NetworkID"];
		}
	}

	repeated NetworkAttachment networks = 8;

	repeated Mount mounts = 9;
}

message RestartPolicy {
	enum RestartCondition {
		option (gogoproto.goproto_enum_prefix) = false;
		option (gogoproto.enum_customname) = "RestartCondition";
		RESTART_NEVER = 0 [(gogoproto.enumvalue_customname) = "RestartNever"];
		RESTART_ON_FAILURE = 1 [(gogoproto.enumvalue_customname) = "RestartOnFailure"];
		RESTART_ALWAYS = 2 [(gogoproto.enumvalue_customname) = "RestartAlways"];
	}

	RestartCondition condition = 1;

	// Delay between restart attempts
	uint64 delay = 2 [(gogoproto.customtype) = "time.Duration", (gogoproto.nullable) = false];

	// TODO(aaronl): Consider additional features such as a limit on the
	// number of restart attempts, and a time period after which the
	// failure count is reset.
}

// UpdateStrategy specifies the rate and policy of updates.
// TODO(aluzzardi): Consider making this a oneof with RollingStrategy and LockstepStrategy.
message UpdateStrategy {
	// Maximum number of tasks to be updated in one iteration.
	// 0 means unlimited parallelism.
	uint64 parallelism = 1;

	// Amount of time between updates.
	uint64 delay = 2 [(gogoproto.customtype) = "time.Duration", (gogoproto.nullable) = false];
}

// TaskState enumerates the states that a task progresses through within an
// agent. States are designed to be monotonically increasing, such that if two
// states are seen by a task, the greater of the new represents the true state.
enum TaskState {
	// TODO(aluzzardi): Move it back into `TaskStatus` because of the naming
	// collisions of enums.

	option (gogoproto.goproto_enum_prefix) = false;
	option (gogoproto.enum_customname) = "TaskState";
	NEW = 0 [(gogoproto.enumvalue_customname)="TaskStateNew"];
	ALLOCATED = 64 [(gogoproto.enumvalue_customname)="TaskStateAllocated"]; // successful allocation of resources that the task needs
	PENDING = 128 [(gogoproto.enumvalue_customname) = "TaskStatePending"]; // observed by scheduler but unassigned.
	ASSIGNED = 192 [(gogoproto.enumvalue_customname)="TaskStateAssigned"];
	ACCEPTED = 256 [(gogoproto.enumvalue_customname)="TaskStateAccepted"]; // task has been accepted by an agent.
	PREPARING = 320 [(gogoproto.enumvalue_customname)="TaskStatePreparing"];
	READY = 384 [(gogoproto.enumvalue_customname)="TaskStateReady"];
	STARTING = 448 [(gogoproto.enumvalue_customname)="TaskStateStarting"];
	RUNNING = 512 [(gogoproto.enumvalue_customname)="TaskStateRunning"];
	SHUTDOWN = 576 [(gogoproto.enumvalue_customname)="TaskStateShutdown"];
	COMPLETE = 640 [(gogoproto.enumvalue_customname)="TaskStateCompleted"]; // successful completion of task (not error code, just ran)
	FAILED = 704 [(gogoproto.enumvalue_customname)="TaskStateFailed"]; // task execution failed with error
	REJECTED = 768 [(gogoproto.enumvalue_customname)="TaskStateRejected"]; // task could not be executed here.
	FINALIZE = 832 [(gogoproto.enumvalue_customname)="TaskStateFinalize"]; // when task is deallocated from node, waiting on cleanup
	DEAD = 896 [(gogoproto.enumvalue_customname)="TaskStateDead"]; // completely finished, unallocated task.

	// NOTE(stevvooe): The state of a task is actually a lamport clock, in that
	// given two observations, the greater of the two can be considered
	// correct. To enforce this, we only allow tasks to proceed to a greater
	// state.
	//
	// A byproduct of this design decision is that we must also maintain this
	// invariant in the protobuf enum values, such that when comparing two
	// values, the one with the greater value is also the greater state.
	//
	// Because we may want to add intervening states a later date, we've left
	// 64 spaces between each one. This should allow us to make 5 or 6
	// insertions between each state if we find that we made a mistake and need
	// another state.
	//
	// Remove this message when the states are deemed perfect.
}

message TaskStatus {
	Timestamp timestamp = 1;

	// State expresses the current state of the task.
	TaskState state = 2;

	// TerminalState indicates the state which led to the task stopping,
	// i.e Rejected, Complete or Failed. If the task is not in a terminal
	// state, this is set to New (0).
	TaskState terminal_state = 3;

	// Message reports a message for the task status. This should provide a
	// human readable message that can point to how the task actually arrived
	// at a current state.
	//
	// As a convention, we place the a small message here that led to the
	// current state. For example, if the task is in ready, because it was
	// prepared, we'd place "prepared" in this field. If we skipped preparation
	// because the task is prepared, we would put "already prepared" in this
	// field.
	string message = 4;

	// Err is set if the task is in an error state.
	//
	// The following states should report a companion error:
	//
	//	FAILED, REJECTED
	//
	// TODO(stevvooe) Integrate this field with the error interface.
	string err = 5;

	// TODO(stevvooe): Report on runtime statistics.
}

// IPAMConfiguration specifies parameters for IP Address Management.
message IPAMConfiguration {
	// TODO(stevvooe): It may make more sense to manage IPAM and network
	// definitions separately. This will allow multiple networks to share IPAM
	// instances. For now, we will follow the conventions of libnetwork and
	// specify this as part of the network specification.

	// AddressFamily specifies the network address family that
	// this IPAMConfiguration belongs to.
	enum AddressFamily {
		UNKNOWN = 0; // satisfy proto3
		IPV4 = 4;
		IPV6 = 6;
	}

	AddressFamily family = 1;

	// Subnet defines a network as a CIDR address (ie network and mask
	// 192.168.0.1/24).
	string subnet = 2;

	// Range defines the portion of the subnet to allocate to tasks. This is
	// defined as a subnet within the primary subnet.
	string range = 3;

	// Gateway address within the subnet.
	string gateway = 4;

	// Reserved is a list of address from the master pool that should *not* be
	// allocated. These addresses may have already been allocated or may be
	// reserved for another allocation manager.
	map<string, string> reserved = 5;
}

// Endpoint specified all the network parameters required to
// correctly discover and load balance a service
message Endpoint {
	// Layer 4 port the port configuration belongs to.
	enum Protocol {
		TCP = 0;
		UDP = 1;
	}

	// PortConfiguration specifies an exposed port which can be
	// addressed using the given name. This can be later queried
	// using a service discovery api or a DNS SRV query. The node
	// port specifies a port that can be used to address this
	// service external to the cluster by sending a connection
	// request to this port to any node on the cluster.
	message PortConfiguration {
		// Name for the port. If provided the port information can
		// be queried using the name as in a DNS SRV query.
		string name = 1;

		// The port which the application is exposing and is bound to.
		uint32 port = 2;

		// Protocol for the port which is exposed.
		Protocol protocol = 3;

		// A cluster-wide unique port which is assigned to
		// this port configuration of the service used for
		// requests ingressing into the cluster for this
		// service port.
		uint32 node_port = 4;
	}

	// A virtual IP is used to address this service in IP layer
	// that the client can use to send requests to this service. A
	// DNS A/AAAA query on the service name might return this IP
	// to the client. This is strictly a logical IP and there may
	// not be any interfaces assigned this IP address or any route
	// created for this address.  More than one to accomodate for
	// both IPv4 and IPv6
	repeated string virtual_ip = 1 [(gogoproto.customname) = "VirtualIP"];
	repeated PortConfiguration ports = 2;
}

// Driver is a generic driver type to be used throughout the API. For now, a
// driver is simply a name and set of options. The field contents depend on the
// target use case and driver application. For example, a network driver may
// have different rules than a volume driver.
message Driver {
	string name = 1;
	map <string, string> options = 2;
}

message IPAMOptions {
	Driver driver = 1;
	repeated IPAMConfiguration configurations = 3;
}

// WeightedPeer should be used anywhere where we are describing a remote peer
// with a weight.
message WeightedPeer {
	string addr = 1;
	int64 weight = 2;

	// TODO(stevvooe): We must consolidate peer representation in the API.
}
