diff --git a/Consul/Agent.cs b/Consul/Agent.cs index 4c826d7..1380882 100644 --- a/Consul/Agent.cs +++ b/Consul/Agent.cs @@ -358,6 +358,9 @@ private class CheckUpdate public string Output { get; set; } } + /// + /// AgentToken is used when updating ACL tokens for an agent. + /// private class AgentToken { [JsonProperty] @@ -665,6 +668,133 @@ internal Agent(ConsulClient c) var res = await req.ExecuteStreaming(ct).ConfigureAwait(false); return new LogStream(res.Response); } + + /// + /// UpdateACLToken updates the agent's "acl_token". + /// for more details. + /// + /// + /// + /// + /// + /// + public Task UpdateACLToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + return UpdateToken("acl_token", token, q, ct); + } + + /// + /// UpdateACLToken updates the agent's "acl_token". + /// for more details. + /// + /// + /// + /// + /// + public Task UpdateACLToken(string token, CancellationToken ct = default(CancellationToken)) + { + return UpdateACLToken(token, WriteOptions.Default, ct); + } + + /// + /// UpdateACLAgentToken updates the agent's "acl_agent_token". + /// for more details. + /// + /// + /// + /// + /// + /// + public Task UpdateACLAgentToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + return UpdateToken("acl_agent_token", token, q, ct); + } + + /// + /// UpdateACLAgentToken updates the agent's "acl_agent_token". + /// for more details. + /// + /// + /// + /// + /// + public Task UpdateACLAgentToken(string token, CancellationToken ct = default(CancellationToken)) + { + return UpdateACLAgentToken(token, WriteOptions.Default, ct); + } + + /// + /// UpdateACLAgentMasterToken updates the agent's "acl_agent_master_token". + /// for more details. + /// + /// + /// + /// + /// + /// + public Task UpdateACLAgentMasterToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + return UpdateToken("acl_agent_master_token", token, q, ct); + } + + /// + /// UpdateACLAgentMasterToken updates the agent's "acl_agent_master_token". + /// for more details. + /// + /// + /// + /// + /// + public Task UpdateACLAgentMasterToken(string token, CancellationToken ct = default(CancellationToken)) + { + return UpdateACLAgentMasterToken(token, WriteOptions.Default, ct); + } + + /// + /// UpdateACLReplicationToken updates the agent's "acl_replication_token". + /// for more details. + /// + /// + /// + /// + /// + /// + public Task UpdateACLReplicationToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + return UpdateToken("acl_replication_token", token, q, ct); + } + + /// + /// UpdateACLReplicationToken updates the agent's "acl_replication_token". + /// for more details. + /// + /// + /// + /// + /// + /// + public Task UpdateACLReplicationToken(string token, CancellationToken ct = default(CancellationToken)) + { + return UpdateACLReplicationToken(token, WriteOptions.Default, ct); + } + + /// + /// UpdateToken can be used to update an agent's ACL token after the agent has + /// started. The tokens are not persisted, so will need to be updated again if + /// the agent is restarted. + /// + /// + /// + /// + /// + /// + private Task UpdateToken(string target, string token, WriteOptions q, CancellationToken ct) + { + var req = _client.Put(string.Format("/v1/agent/token/{0}", target), + new AgentToken {Token = token}, q); + + return req.Execute(ct); + } public class LogStream : IEnumerable>, IDisposable { diff --git a/Consul/Interfaces/IAgentEndpoint.cs b/Consul/Interfaces/IAgentEndpoint.cs index cc363e5..ff4041f 100644 --- a/Consul/Interfaces/IAgentEndpoint.cs +++ b/Consul/Interfaces/IAgentEndpoint.cs @@ -49,5 +49,13 @@ public interface IAgentEndpoint Task Leave(string node, CancellationToken ct = default(CancellationToken)); Task Reload(CancellationToken ct = default(CancellationToken)); Task> Metrics(CancellationToken ct = default(CancellationToken)); + Task UpdateACLToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task UpdateACLToken(string token, CancellationToken ct = default(CancellationToken)); + Task UpdateACLAgentToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task UpdateACLAgentToken(string token, CancellationToken ct = default(CancellationToken)); + Task UpdateACLAgentMasterToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task UpdateACLAgentMasterToken(string token, CancellationToken ct = default(CancellationToken)); + Task UpdateACLReplicationToken(string token, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task UpdateACLReplicationToken(string token, CancellationToken ct = default(CancellationToken)); } } diff --git a/Consul/Interfaces/IOperatorEndpoint.cs b/Consul/Interfaces/IOperatorEndpoint.cs index 61af316..fbc9023 100644 --- a/Consul/Interfaces/IOperatorEndpoint.cs +++ b/Consul/Interfaces/IOperatorEndpoint.cs @@ -20,5 +20,29 @@ public interface IOperatorEndpoint Task KeyringRemove(string key, WriteOptions q, CancellationToken ct = default(CancellationToken)); Task KeyringUse(string key, CancellationToken ct = default(CancellationToken)); Task KeyringUse(string key, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task> AreaCreate(Area area, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task> AreaCreate(Area area, CancellationToken ct = default(CancellationToken)); + Task AreaDelete(string areaID, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task AreaDelete(string areaID, CancellationToken ct = default(CancellationToken)); + Task> AreaGet(string areaID, QueryOptions q, CancellationToken ct = default(CancellationToken)); + Task> AreaGet(string areaID, CancellationToken ct = default(CancellationToken)); + Task> AreaJoin(string areaID, string[] addresses, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task> AreaJoin(string areaID, string[] addresses, CancellationToken ct = default(CancellationToken)); + Task> AreaList(QueryOptions q, CancellationToken ct = default(CancellationToken)); + Task> AreaList(CancellationToken ct = default(CancellationToken)); + Task> AreaMembers(string areaID, QueryOptions q, CancellationToken ct = default(CancellationToken)); + Task> AreaMembers(string areaID, CancellationToken ct = default(CancellationToken)); + Task> AreaUpdate(string areaID, Area area, WriteOptions q, CancellationToken ct = default (CancellationToken)); + Task> AreaUpdate(string areaID, Area area, CancellationToken ct = default (CancellationToken)); + Task> SegmentList(QueryOptions q, CancellationToken ct = default(CancellationToken)); + Task> SegmentList(CancellationToken ct = default(CancellationToken)); + Task AutopilotCASConfiguration(AutopilotConfiguration conf, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task AutopilotCASConfiguration(AutopilotConfiguration conf, CancellationToken ct = default(CancellationToken)); + Task AutopilotGetConfiguration(QueryOptions q, CancellationToken ct = default(CancellationToken)); + Task AutopilotGetConfiguration(CancellationToken ct = default(CancellationToken)); + Task AutopilotSetConfiguration(AutopilotConfiguration conf, WriteOptions q, CancellationToken ct = default(CancellationToken)); + Task AutopilotSetConfiguration(AutopilotConfiguration conf, CancellationToken ct = default(CancellationToken)); + Task AutopilotServerHealth(QueryOptions q, CancellationToken ct = default(CancellationToken)); + Task AutopilotServerHealth(CancellationToken ct = default(CancellationToken)); } } diff --git a/Consul/Operator.cs b/Consul/Operator.cs index dd49b4e..970889d 100644 --- a/Consul/Operator.cs +++ b/Consul/Operator.cs @@ -1,9 +1,11 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; namespace Consul { @@ -82,6 +84,276 @@ public class KeyringResponse /// public int NumNodes { get; set; } } + + /// + /// Area defines a network area. + /// + public class Area + { + /// + /// ID is this identifier for an area (a UUID). This must be left empty + /// when creating a new area. + /// + public string ID { get; set; } + + /// + /// PeerDatacenter is the peer Consul datacenter that will make up the + /// other side of this network area. Network areas always involve a pair + /// of datacenters: the datacenter where the area was created, and the + /// peer datacenter. This is required. + /// + public string PeerDatacenter { get; set; } + + /// + /// RetryJoin specifies the address of Consul servers to join to, such as + /// an IPs or hostnames with an optional port number. This is optional. + /// + public string[] RetryJoin { get; set; } + } + + /// + /// AreaJoinResponse is returned when a join occurs and gives the result for each + /// address. + /// + public class AreaJoinResponse + { + /// + /// The address that was joined. + /// + public string Address { get; set; } + + /// + /// Whether or not the join was a success. + /// + public bool Joined { get; set; } + + /// + /// If we couldn't join, this is the message with information. + /// + public string Error { get; set; } + } + + /// + /// SerfMember is a generic structure for reporting information about members in + /// a Serf cluster. This is only used by the area endpoints right now, but this + /// could be expanded to other endpoints in the future. + /// + public class SerfMember + { + /// + /// ID is the node identifier (a UUID). + /// + public string ID { get; set; } + + /// + /// Name is the node name. + /// + public string Name { get; set; } + + /// + /// Addr has the IP address. + /// + public string Addr { get; set; } + + /// + /// Port is the RPC port. + /// + public ushort Port { get; set; } + + /// + /// Datacenter is the DC name. + /// + public string Datacenter { get; set; } + + /// + /// Role is "client", "server", or "unknown". + /// + public string Role { get; set; } + + /// + /// Build has the version of the Consul agent. + /// + public string Build { get; set; } + + /// + /// Protocol is the protocol of the Consul agent. + /// + public int Protocol { get; set; } + + /// + /// Status is the Serf health status "none", "alive", "leaving", "left", + /// or "failed". + /// + public string Status { get; set; } + + /// + /// RTT is the estimated round trip time from the server handling the + /// request to the this member. This will be negative if no RTT estimate + /// is available. + /// + [JsonConverter(typeof(NanoSecTimespanConverter))] + public TimeSpan RTT { get; set; } + } + + /// + /// AutopilotConfiguration is used for querying/setting the Autopilot configuration. + /// Autopilot helps manage operator tasks related to Consul servers like removing + /// failed servers from the Raft quorum. + /// + public class AutopilotConfiguration + { + /// + /// CleanupDeadServers controls whether to remove dead servers from the Raft + /// peer list when a new server joins + /// + public bool CleanupDeadServers { get; set; } + + /// + /// LastContactThreshold is the limit on the amount of time a server can go + /// without leader contact before being considered unhealthy. + /// + [JsonConverter(typeof(DurationTimespanConverter))] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public TimeSpan? LastContactThreshold { get; set; } + + /// + /// MaxTrailingLogs is the amount of entries in the Raft Log that a server can + /// be behind before being considered unhealthy. + /// + public ulong MaxTrailingLogs { get; set; } + + /// + /// ServerStabilizationTime is the minimum amount of time a server must be + /// in a stable, healthy state before it can be added to the cluster. Only + /// applicable with Raft protocol version 3 or higher. + /// + [JsonConverter(typeof(DurationTimespanConverter))] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public TimeSpan? ServerStabilizationTime { get; set; } + + /// + /// (Enterprise-only) RedundancyZoneTag is the node tag to use for separating + /// servers into zones for redundancy. If left blank, this feature will be disabled. + /// + public string RedundancyZoneTag { get; set; } + + /// + /// (Enterprise-only) DisableUpgradeMigration will disable Autopilot's upgrade migration + /// strategy of waiting until enough newer-versioned servers have been added to the + /// cluster before promoting them to voters. + /// + public bool DisableUpgradeMigration { get; set; } + + /// + /// (Enterprise-only) UpgradeVersionTag is the node tag to use for version info when + /// performing upgrade migrations. If left blank, the Consul version will be used. + /// + public string UpgradeVersionTag { get; set; } + + /// + /// CreateIndex holds the index corresponding the creation of this configuration. + /// This is a read-only field. + /// + public ulong CreateIndex { get; set; } + + /// + /// ModifyIndex will be set to the index of the last update when retrieving the + /// Autopilot configuration. Resubmitting a configuration with + /// AutopilotCASConfiguration will perform a check-and-set operation which ensures + /// there hasn't been a subsequent update since the configuration was retrieved. + /// + public ulong ModifyIndex { get; set; } + } + + /// + /// ServerHealth is the health (from the leader's point of view) of a server. + /// + public class ServerHealth + { + /// + /// ID is the raft ID of the server. + /// + public string ID { get; set; } + + /// + /// Name is the node name of the server. + /// + public string Name { get; set; } + + /// + /// Address is the address of the server. + /// + public string Address { get; set; } + + /// + /// The status of the SerfHealth check for the server. + /// + public string SerfStatus { get; set; } + + /// + /// Version is the Consul version of the server. + /// + public string Version { get; set; } + + /// + /// Leader is whether this server is currently the leader. + /// + public string Leader { get; set; } + + /// + /// LastContact is the time since this node's last contact with the leader. + /// + [JsonConverter(typeof(DurationTimespanConverter))] + public TimeSpan LastContact { get; set; } + + /// + /// LastTerm is the highest leader term this server has a record of in its Raft log. + /// + public ulong LastTerm { get; set; } + + /// + /// LastIndex is the last log index this server has a record of in its Raft log. + /// + public ulong LastIndex { get; set; } + + /// + /// Healthy is whether or not the server is healthy according to the current + /// Autopilot config. + /// + public bool Healthy { get; set; } + + /// + /// Voter is whether this is a voting server. + /// + public bool Voter { get; set; } + + /// + /// StableSince is the last time this server's Healthy value changed. + /// + public DateTime StableSince { get; set; } + } + + /// + /// OperatorHealthReply is a representation of the overall health of the cluster + /// + public class OperatorHealthReply + { + /// + /// Healthy is true if all the servers in the cluster are healthy. + /// + public bool Healthy { get; set; } + + /// + /// FailureTolerance is the number of healthy servers that could be lost without + /// an outage occurring. + /// + public int FailureTolerance { get; set; } + + /// + /// Servers holds the health of each server. + /// + public ServerHealth[] Servers { get; set; } + } public class Operator : IOperatorEndpoint { @@ -95,6 +367,12 @@ internal Operator(ConsulClient c) { _client = c; } + + private class AreaCreationResult + { + [JsonProperty] + internal string ID { get; set; } + } /// /// KeyringRequest is used for performing Keyring operations @@ -211,6 +489,330 @@ private class KeyringRequest { return _client.Put("/v1/operator/keyring", new KeyringRequest() { Key = key }, q).Execute(ct); } + + /// + /// AreaCreate will create a new network area. The ID in the given structure must + /// be empty and a generated ID will be returned on success. + /// + /// + /// + /// + /// + /// + public async Task> AreaCreate(Area area, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + var res = await _client.Post("/v1/operator/area", area, q).Execute(ct) + .ConfigureAwait(false); + + return new WriteResult(res, res.Response.ID); + } + + /// + /// AreaCreate will create a new network area. The ID in the given structure must + /// be empty and a generated ID will be returned on success. + /// + /// + /// + /// + /// + public Task> AreaCreate(Area area, CancellationToken ct = default(CancellationToken)) + { + return AreaCreate(area, WriteOptions.Default, ct); + } + + /// + /// AreaDelete deletes the given network area. + /// + /// + /// + /// + /// + /// + public Task AreaDelete(string areaID, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + return _client.Delete(string.Format("/v1/operator/area/{0}", areaID)).Execute(ct); + } + + /// + /// AreaDelete deletes the given network area. + /// + /// + /// + /// + /// + public Task AreaDelete(string areaID, CancellationToken ct = default(CancellationToken)) + { + return AreaDelete(areaID, WriteOptions.Default, ct); + } + + /// + /// AreaGet returns a single network area. + /// + /// + /// + /// + /// + /// + public Task> AreaGet(string areaID, QueryOptions q, CancellationToken ct = default(CancellationToken)) + { + return _client.Get(string.Format("/v1/operator/area/{0}", areaID), q).Execute(ct); + } + + /// + /// AreaGet returns a single network area. + /// + /// + /// + /// + /// + public Task> AreaGet(string areaID, CancellationToken ct = default(CancellationToken)) + { + return AreaGet(areaID, QueryOptions.Default, ct); + } + + /// + /// AreaJoin attempts to join the given set of join addresses to the given + /// network area. See the Area class for details about join addresses. + /// + /// + /// + /// + /// + /// + /// + public Task> AreaJoin(string areaID, string[] addresses, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + return _client + .Put(string.Format("/v1/operator/area/{0}/join", areaID), addresses, q) + .Execute(ct); + } + + /// + /// AreaJoin attempts to join the given set of join addresses to the given + /// network area. See the Area class for details about join addresses. + /// + /// + /// + /// + /// + /// + public Task> AreaJoin(string areaID, string[] addresses, CancellationToken ct = default(CancellationToken)) + { + return AreaJoin(areaID, addresses, WriteOptions.Default, ct); + } + + /// + /// AreaList returns all the available network areas. + /// + /// + /// + /// + /// + public Task> AreaList(QueryOptions q, CancellationToken ct = default(CancellationToken)) + { + return _client.Get("/v1/operator/area").Execute(ct); + } + + /// + /// AreaList returns all the available network areas. + /// + /// + /// + /// + public Task> AreaList(CancellationToken ct = default(CancellationToken)) + { + return AreaList(QueryOptions.Default, ct); + } + + /// + /// AreaMembers lists the Serf information about the members in the given area. + /// + /// + /// + /// + /// + /// + public Task> AreaMembers(string areaID, QueryOptions q, CancellationToken ct = default(CancellationToken)) + { + return _client.Get(string.Format("/v1/operator/area/{0}/members", areaID), q).Execute(ct); + } + + /// + /// AreaMembers lists the Serf information about the members in the given area. + /// + /// + /// + /// + /// + public Task> AreaMembers(string areaID, CancellationToken ct = default(CancellationToken)) + { + return AreaMembers(areaID, QueryOptions.Default, ct); + } + + /// + /// AreaUpdate will update the configuration of the network area with the given ID. + /// + /// + /// + /// + /// + /// + /// + public async Task> AreaUpdate(string areaID, Area area, WriteOptions q, CancellationToken ct = default(CancellationToken)) + { + var res = await _client.Put(string.Format("/v1/operator/area/{0}", areaID), area, q) + .Execute(ct).ConfigureAwait(false); + + return new WriteResult(res, res.Response.ID); + } + + /// + /// AreaUpdate will update the configuration of the network area with the given ID. + /// + /// + /// + /// + /// + /// + public Task> AreaUpdate(string areaID, Area area, CancellationToken ct = default(CancellationToken)) + { + return AreaUpdate(areaID, area, WriteOptions.Default, ct); + } + + /// + /// SegmentList returns all the available LAN segments. + /// + /// + /// + /// + /// + public Task> SegmentList(QueryOptions q, CancellationToken ct = default(CancellationToken)) + { + return _client.Get("/v1/operator/segment", q).Execute(ct); + } + + /// + /// SegmentList returns all the available LAN segments. + /// + /// + /// + /// + public Task> SegmentList(CancellationToken ct = default(CancellationToken)) + { + return SegmentList(QueryOptions.Default, ct); + } + + /// + /// AutopilotCASConfiguration is used to perform a Check-And-Set update on the + /// Autopilot configuration. The ModifyIndex value will be respected. Returns + /// true on success or false on failures. + /// + /// + /// + /// + /// + public async Task AutopilotCASConfiguration(AutopilotConfiguration conf, WriteOptions q, + CancellationToken ct = default(CancellationToken)) + { + //TODO: Maybe there is a better way to execute the request + var req = _client.Put("/v1/operator/autopilot/configuration", conf, q); + req.Params["cas"] = conf.ModifyIndex.ToString(); + + await req.Execute(ct).ConfigureAwait(false); + + using (var reader = new StreamReader(req.ResponseStream)) + { + var body = await reader.ReadToEndAsync().ConfigureAwait(false); + return body.Contains("true"); + } + } + + /// + /// AutopilotCASConfiguration is used to perform a Check-And-Set update on the + /// Autopilot configuration. The ModifyIndex value will be respected. Returns + /// true on success or false on failures. + /// + /// + /// + /// + public Task AutopilotCASConfiguration(AutopilotConfiguration conf, CancellationToken ct = default(CancellationToken)) + { + return AutopilotCASConfiguration(conf, WriteOptions.Default, ct); + } + + /// + /// AutopilotGetConfiguration is used to query the current Autopilot configuration. + /// + /// + /// + /// + /// + public async Task AutopilotGetConfiguration(QueryOptions q, CancellationToken ct = default(CancellationToken)) + { + var res = await _client.Get("/v1/operator/autopilot/configuration", q).Execute(ct).ConfigureAwait(false); + return res.Response; + } + + /// + /// AutopilotGetConfiguration is used to query the current Autopilot configuration. + /// + /// + /// + /// + public Task AutopilotGetConfiguration(CancellationToken ct = default(CancellationToken)) + { + return AutopilotGetConfiguration(QueryOptions.Default, ct); + } + + /// + /// AutopilotSetConfiguration is used to set the current Autopilot configuration. + /// + /// + /// + /// + /// + /// + public async Task AutopilotSetConfiguration(AutopilotConfiguration conf, WriteOptions q, + CancellationToken ct = default(CancellationToken)) + { + await _client.Put("/v1/operator/autopilot/configuration", conf, q).Execute(ct).ConfigureAwait(false); + } + + /// + /// AutopilotSetConfiguration is used to set the current Autopilot configuration. + /// + /// + /// + /// + /// + public Task AutopilotSetConfiguration(AutopilotConfiguration conf, CancellationToken ct = default(CancellationToken)) + { + return AutopilotSetConfiguration(conf, WriteOptions.Default, ct); + } + + /// + /// AutopilotServerHealth + /// + /// + /// + /// + /// + public async Task AutopilotServerHealth(QueryOptions q, CancellationToken ct = default(CancellationToken)) + { + var resp = await _client.Get("/v1/operator/autopilot/health", q).Execute(ct).ConfigureAwait(false); + return resp.Response; + } + + /// + /// AutopilotServerHealth + /// + /// + /// + /// + public Task AutopilotServerHealth(CancellationToken ct = default(CancellationToken)) + { + return AutopilotServerHealth(QueryOptions.Default, ct); + } } public partial class ConsulClient : IConsulClient diff --git a/Consul/Utilities/JsonConverters.cs b/Consul/Utilities/JsonConverters.cs index 6018f02..7c106e0 100644 --- a/Consul/Utilities/JsonConverters.cs +++ b/Consul/Utilities/JsonConverters.cs @@ -18,9 +18,7 @@ using System; using Newtonsoft.Json; -using System.Reflection; -using System.Linq; -using System.Collections.Generic; +using System.Globalization; namespace Consul { diff --git a/appveyor.yml b/appveyor.yml index f5a9911..a588f12 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,7 +15,7 @@ assembly_info: assembly_informational_version: $(APPVEYOR_BUILD_VERSION) environment: CONSUL_BIN: c:\consul\consul.test\consul.exe - CONSUL_VERSION: 0.7.2 + CONSUL_VERSION: 0.9.3 CLI_VERSION: Latest install: - cinst: 7zip.commandline