public async Task <QueryResult <string> > TranslateLegacyTokenRules(string id, QueryOptions q, CancellationToken ct = default(CancellationToken)) { var res = await _client.Get($"/v1/acl/rules/translate/{id}", q).Execute(ct).ConfigureAwait(false); return(new QueryResult <string>(res, res.Response)); }
/// <summary> /// Node gets all sessions for a node /// </summary> /// <param name="node">The node ID</param> /// <param name="q">Customized query options</param> /// <returns>A query result containing the list of sessions, or an empty query result if no sessions exist</returns> public Task <QueryResult <SessionEntry[]> > Node(string node, QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <SessionEntry[]>(string.Format("/v1/session/node/{0}", node), q).Execute(ct)); }
/// <summary> /// Gets an existing ACL Token from Consul /// </summary> /// <param name="id">The Accessor ID of the ACL Token to get</param> /// <param name="queryOptions">Customized query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A query result containing the requested ACL Token</returns> public async Task <QueryResult <TokenEntry> > Read(string id, QueryOptions queryOptions, CancellationToken ct = default(CancellationToken)) { var res = await _client.Get <TokenActionResult>($"/v1/acl/token/{id}", queryOptions).Execute(ct).ConfigureAwait(false); return(new QueryResult <TokenEntry>(res, res.Response)); }
/// <summary> /// Service is used to query catalog entries for a given service /// </summary> /// <param name="service">The service ID</param> /// <param name="tag">A tag to filter on</param> /// <param name="q">Customized query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A list of service instances</returns> public Task <QueryResult <CatalogService[]> > Service(string service, string tag, QueryOptions q, CancellationToken ct) { var req = _client.Get <CatalogService[]>(string.Format("/v1/catalog/service/{0}", service), q); if (!string.IsNullOrEmpty(tag)) { req.Params["tag"] = tag; } return(req.Execute(ct)); }
/// <summary> /// Info looks up a single session /// </summary> /// <param name="id">The session ID to look up</param> /// <param name="q">Customized query options</param> /// <returns>A query result containing the session information, or an empty query result if the session entry does not exist</returns> public async Task <QueryResult <SessionEntry> > Info(string id, QueryOptions q, CancellationToken ct = default(CancellationToken)) { var res = await _client.Get <SessionEntry[]>(string.Format("/v1/session/info/{0}", id), q).Execute(ct).ConfigureAwait(false); return(new QueryResult <SessionEntry>(res, res.Response != null && res.Response.Length > 0 ? res.Response[0] : null)); }
/// <summary> /// RaftGetConfiguration is used to query the current Raft peer set. /// </summary> public Task <QueryResult <RaftConfiguration> > RaftGetConfiguration(QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <RaftConfiguration>("/v1/operator/raft/configuration", q).Execute(ct)); }
/// <summary> /// Nodes is used to query all the known nodes /// </summary> /// <param name="q">Customized query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A list of all nodes</returns> public Task <QueryResult <Node[]> > Nodes(QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <Node[]>("/v1/catalog/nodes", q).Execute(ct)); }
/// <summary> /// Gets the requested ACL Role from Consul /// </summary> /// <param name="name">The Name of the ACL Role to get</param> /// <param name="queryOptions">Customised query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A query result containing the requested ACL Role</returns> public async Task <QueryResult <RoleEntry> > ReadByName(string name, QueryOptions queryOptions, CancellationToken ct = default(CancellationToken)) { var res = await _client.Get <RoleEntry>($"/v1/acl/role/name/{name}", queryOptions).Execute(ct).ConfigureAwait(false); return(new QueryResult <RoleEntry>(res, res.Response)); }
/// <summary> /// Query is used to do a GET request against an endpoint and deserialize the response into an interface using standard Consul conventions. /// </summary> /// <param name="endpoint">The URL endpoint to access</param> /// <param name="q">Custom query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>The data returned by the custom endpoint</returns> public Task <QueryResult <dynamic> > Query(string endpoint, QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <dynamic>(endpoint, q).Execute(ct)); }
/// <summary> /// Acquire attempts to reserve a slot in the semaphore, blocking until success, interrupted via CancellationToken or if an error is encountered. /// A provided CancellationToken can be used to abort the attempt. /// There is no notification that the semaphore slot has been lost, but IsHeld may be set to False at any time due to session invalidation, communication errors, operator intervention, etc. /// It is NOT safe to assume that the slot is held until Release() unless the Session is specifically created without any associated health checks. /// By default Consul sessions prefer liveness over safety and an application must be able to handle the session being lost. /// </summary> /// <param name="ct">The cancellation token to cancel semaphore acquisition</param> public CancellationToken Acquire(CancellationToken ct) { lock (_lock) { try { if (IsHeld) { // Check if we already hold the lock throw new SemaphoreHeldException(); } // Don't overwrite the CancellationTokenSource until AFTER we've tested for holding, since there might be tasks that are currently running for this lock. if (_cts.IsCancellationRequested) { _cts.Dispose(); _cts = new CancellationTokenSource(); } _cts = new CancellationTokenSource(); LockSession = Opts.Session; // Check if we need to create a session first if (string.IsNullOrEmpty(Opts.Session)) { try { Opts.Session = CreateSession().GetAwaiter().GetResult(); _sessionRenewTask = _client.Session.RenewPeriodic(Opts.SessionTTL, Opts.Session, WriteOptions.Default, _cts.Token); LockSession = Opts.Session; } catch (Exception ex) { throw new InvalidOperationException("Failed to create session", ex); } } else { LockSession = Opts.Session; } var contender = _client.KV.Acquire(ContenderEntry(LockSession)).GetAwaiter().GetResult().Response; if (!contender) { throw new ApplicationException("Failed to make contender entry"); } var qOpts = new QueryOptions() { WaitTime = Opts.SemaphoreWaitTime }; var attempts = 0; var start = DateTime.UtcNow; while (!ct.IsCancellationRequested) { if (attempts > 0 && Opts.SemaphoreTryOnce) { var elapsed = DateTime.UtcNow.Subtract(start); if (elapsed > qOpts.WaitTime) { throw new SemaphoreMaxAttemptsReachedException("SemaphoreTryOnce is set and the semaphore is already at maximum capacity"); } qOpts.WaitTime -= elapsed; } attempts++; QueryResult <KVPair[]> pairs; try { pairs = _client.KV.List(Opts.Prefix, qOpts).GetAwaiter().GetResult(); } catch (Exception ex) { throw new ApplicationException("Failed to read prefix", ex); } var lockPair = FindLock(pairs.Response); if (lockPair.Flags != SemaphoreFlagValue) { throw new SemaphoreConflictException(); } var semaphoreLock = DecodeLock(lockPair); if (semaphoreLock.Limit != Opts.Limit) { throw new SemaphoreLimitConflictException( string.Format("Semaphore limit conflict (lock: {0}, local: {1})", semaphoreLock.Limit, Opts.Limit), semaphoreLock.Limit, Opts.Limit); } PruneDeadHolders(semaphoreLock, pairs.Response); if (semaphoreLock.Holders.Count >= semaphoreLock.Limit) { qOpts.WaitIndex = pairs.LastIndex; continue; } semaphoreLock.Holders[LockSession] = true; var newLock = EncodeLock(semaphoreLock, lockPair.ModifyIndex); if (ct.IsCancellationRequested) { _cts.Cancel(); throw new TaskCanceledException(); } // Handle the case of not getting the lock if (!_client.KV.CAS(newLock).GetAwaiter().GetResult().Response) { continue; } IsHeld = true; MonitorLock(LockSession); return(_cts.Token); } throw new SemaphoreNotHeldException("Unable to acquire the semaphore with Consul"); } finally { if (ct.IsCancellationRequested || (!IsHeld && !string.IsNullOrEmpty(Opts.Session))) { _cts.Cancel(); _client.KV.Delete(ContenderEntry(LockSession).Key).GetAwaiter().GetResult(); if (_sessionRenewTask != null) { try { _sessionRenewTask.Wait(); } catch (AggregateException) { // Ignore AggregateExceptions from the tasks during Release, since if the Renew task died, the developer will be Super Confused if they see the exception during Release. } } else { _client.Session.Destroy(Opts.Session).Wait(); } Opts.Session = null; } } } }
/// <summary> /// monitorLock is a long running routine to monitor a semaphore ownership /// It sets IsHeld to false if we lose our slot. /// </summary> /// <param name="lockSession">The session ID to monitor</param> private Task MonitorLock(string lockSession) { return(Task.Run(async() => { try { var opts = new QueryOptions() { Consistency = ConsistencyMode.Consistent }; _retries = Opts.MonitorRetries; while (IsHeld && !_cts.Token.IsCancellationRequested) { try { var pairs = await _client.KV.List(Opts.Prefix, opts).ConfigureAwait(false); if (pairs.Response != null) { _retries = Opts.MonitorRetries; var lockPair = FindLock(pairs.Response); var semaphoreLock = DecodeLock(lockPair); PruneDeadHolders(semaphoreLock, pairs.Response); // Slot is no longer held! Shut down everything. if (!semaphoreLock.Holders.ContainsKey(lockSession)) { IsHeld = false; _cts.Cancel(); return; } // Semaphore is still held, start a blocking query opts.WaitIndex = pairs.LastIndex; continue; } // Failsafe in case the KV store is unavailable else { IsHeld = false; _cts.Cancel(); return; } } catch (Exception) { if (_retries > 0) { await Task.Delay(Opts.MonitorRetryTime, _cts.Token).ConfigureAwait(false); _retries--; opts.WaitIndex = 0; continue; } throw; } } } finally { IsHeld = false; } })); }
/// <summary> /// List is used to get all the ACL tokens /// </summary> /// <param name="q">Customized query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A write result containing the list of all ACLs</returns> public Task <QueryResult <ACLEntry[]> > List(QueryOptions q, CancellationToken ct) { return(_client.Get <ACLEntry[]>("/v1/acl/list", q).Execute(ct)); }
/// <summary> /// List is used to get all the ACL tokens /// </summary> /// <param name="q">Customized query options</param> /// <returns>A write result containing the list of all ACLs</returns> public Task <QueryResult <ACLEntry[]> > List(QueryOptions q) { return(List(q, CancellationToken.None)); }
/// <summary> /// Info is used to query for information about an ACL token /// </summary> /// <param name="id">The ACL ID to request information about</param> /// <param name="q">Customized query options</param> /// <returns>A query result containing the ACL entry matching the provided ID, or a query result with a null response if no token matched the provided ID</returns> public Task <QueryResult <ACLEntry> > Info(string id, QueryOptions q) { return(Info(id, q, CancellationToken.None)); }
/// <summary> /// MonitorLock is a long running routine to monitor a lock ownership. It sets IsHeld to false if we lose our leadership. /// </summary> private Task MonitorLock() { return(Task.Factory.StartNew(async() => { // Copy a reference to _cts since we could end up destroying it before this method returns var cts = _cts; try { var opts = new QueryOptions() { Consistency = ConsistencyMode.Consistent }; _retries = Opts.MonitorRetries; while (IsHeld && !cts.Token.IsCancellationRequested) { try { // Check to see if the current session holds the lock var pair = await _client.KV.Get(Opts.Key, opts).ConfigureAwait(false); if (pair.Response != null) { _retries = Opts.MonitorRetries; // Lock is no longer held! Shut down everything. if (pair.Response.Session != LockSession) { IsHeld = false; DisposeCancellationTokenSource(); return; } // Lock is still held, start a blocking query opts.WaitIndex = pair.LastIndex; continue; } else { // Failsafe in case the KV store is unavailable IsHeld = false; DisposeCancellationTokenSource(); return; } } catch (ConsulRequestException) { if (_retries > 0) { await Task.Delay(Opts.MonitorRetryTime, cts.Token).ConfigureAwait(false); _retries--; opts.WaitIndex = 0; continue; } throw; } catch (OperationCanceledException) { // Ignore and retry since this could be the underlying HTTPClient being swapped out/disposed of. } catch (Exception) { throw; } } } finally { IsHeld = false; DisposeCancellationTokenSource(); } }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap()); }
/// <summary> /// List gets all active sessions /// </summary> /// <param name="q">Customized query options</param> /// <returns>A query result containing the list of sessions, or an empty query result if no sessions exist</returns> public QueryResult <SessionEntry[]> List(QueryOptions q) { return(_client.CreateQuery <SessionEntry[]>("/v1/session/list", q).Execute()); }
/// <summary> /// Lists the existing ACL Policies in Consul /// </summary> /// <param name="queryOptions">Customised query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A query result containing an array of ACL Policies</returns> public async Task <QueryResult <PolicyEntry[]> > List(QueryOptions queryOptions, CancellationToken ct = default(CancellationToken)) { var res = await _client.Get <PolicyEntry[]>("/v1/acl/policies", queryOptions).Execute(ct).ConfigureAwait(false); return(new QueryResult <PolicyEntry[]>(res, res.Response)); }
/// <summary> /// Node gets all sessions for a node /// </summary> /// <param name="node">The node ID</param> /// <param name="q">Customized query options</param> /// <returns>A query result containing the list of sessions, or an empty query result if no sessions exist</returns> public QueryResult <SessionEntry[]> Node(string node, QueryOptions q) { return(_client.CreateQuery <SessionEntry[]>(string.Format("/v1/session/node/{0}", node), q).Execute()); }
/// <summary> /// KeyringList is used to list the gossip keys installed in the cluster /// </summary> public Task <QueryResult <KeyringResponse[]> > KeyringList(QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <KeyringResponse[]>("/v1/operator/keyring", q).Execute(ct)); }
internal GetRequest <TOut> Get <TOut>(string path, QueryOptions opts = null, IEncodable filter = null) { return(new GetRequest <TOut>(this, path, opts ?? QueryOptions.Default, filter)); }
/// <summary> /// Services is used to query for all known services /// </summary> /// <param name="q">Customized query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A list of all services</returns> public Task <QueryResult <Dictionary <string, string[]> > > Services(QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <Dictionary <string, string[]> >("/v1/catalog/services", q).Execute(ct)); }
internal GetRequest Get(string path, QueryOptions opts = null) { return(new GetRequest(this, path, opts ?? QueryOptions.Default)); }
/// <summary> /// Node is used to query for service information about a single node /// </summary> /// <param name="node">The node name</param> /// <param name="q">Customized query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>The node information including a list of services</returns> public Task <QueryResult <CatalogNode> > Node(string node, QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <CatalogNode>(string.Format("/v1/catalog/node/{0}", node), q).Execute(ct)); }
public Task <QueryResult <PreparedQueryExecuteResponse> > Execute(string queryIDOrName, QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <PreparedQueryExecuteResponse>(string.Format("/v1/query/{0}/execute", queryIDOrName), q).Execute(ct)); }
/// <summary> /// List gets all active sessions /// </summary> /// <param name="q">Customized query options</param> /// <returns>A query result containing the list of sessions, or an empty query result if no sessions exist</returns> public Task <QueryResult <SessionEntry[]> > List(QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <SessionEntry[]>("/v1/session/list", q).Execute(ct)); }
public Task <QueryResult <PreparedQueryDefinition[]> > Get(string queryID, QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <PreparedQueryDefinition[]>(string.Format("/v1/query/{0}", queryID), q).Execute(ct)); }
/// <summary> /// Lock attempts to acquire the lock and blocks while doing so. /// Providing a CancellationToken can be used to abort the lock attempt. /// There is no notification that the lock has been lost, but IsHeld may be set to False at any time due to session invalidation, communication errors, operator intervention, etc. /// It is NOT safe to assume that the lock is held until Unlock() unless the Session is specifically created without any associated health checks. /// Users of the Lock object should check the IsHeld property before entering the critical section of their code, e.g. in a "while (myLock.IsHeld) {criticalsection}" block. /// By default Consul sessions prefer liveness over safety and an application must be able to handle the lock being lost. /// </summary> /// <param name="ct">The cancellation token to cancel lock acquisition</param> public CancellationToken Acquire(CancellationToken ct) { lock (_lock) { try { if (IsHeld) { // Check if we already hold the lock throw new LockHeldException(); } // Don't overwrite the CancellationTokenSource until AFTER we've tested for holding, since there might be tasks that are currently running for this lock. if (_cts.IsCancellationRequested) { _cts.Dispose(); _cts = new CancellationTokenSource(); } // Check if we need to create a session first if (string.IsNullOrEmpty(Opts.Session)) { try { Opts.Session = CreateSession().GetAwaiter().GetResult(); _sessionRenewTask = _client.Session.RenewPeriodic(Opts.SessionTTL, Opts.Session, WriteOptions.Default, _cts.Token); LockSession = Opts.Session; } catch (Exception ex) { throw new ConsulRequestException("Failed to create session", ex); } } else { LockSession = Opts.Session; } var qOpts = new QueryOptions() { WaitTime = Opts.LockWaitTime }; var attempts = 0; while (!ct.IsCancellationRequested) { if (attempts > 0 && Opts.LockTryOnce) { throw new LockMaxAttemptsReachedException("LockTryOnce is set and the lock is already held or lock delay is in effect"); } attempts++; QueryResult <KVPair> pair; try { pair = _client.KV.Get(Opts.Key, qOpts).GetAwaiter().GetResult(); } catch (Exception ex) { throw new ConsulRequestException("Failed to read lock key", ex); } if (pair.Response != null) { if (pair.Response.Flags != LockFlagValue) { throw new LockConflictException(); } // Already locked by this session if (pair.Response.Session == LockSession) { // Don't restart MonitorLock if this session already holds the lock if (IsHeld) { return(_cts.Token); } IsHeld = true; MonitorLock(); return(_cts.Token); } // If it's not empty, some other session must have the lock if (!string.IsNullOrEmpty(pair.Response.Session)) { qOpts.WaitIndex = pair.LastIndex; continue; } } // If the code executes this far, no other session has the lock, so try to lock it var kvPair = LockEntry(Opts.Session); var locked = _client.KV.Acquire(kvPair).GetAwaiter().GetResult().Response; // KV acquisition succeeded, so the session now holds the lock if (locked) { IsHeld = true; MonitorLock(); return(_cts.Token); } // Handle the case of not getting the lock if (ct.IsCancellationRequested) { _cts.Cancel(); throw new TaskCanceledException(); } // Failed to get the lock, determine why by querying for the key again qOpts.WaitIndex = 0; pair = _client.KV.Get(Opts.Key, qOpts).GetAwaiter().GetResult(); // If the session is not null, this means that a wait can safely happen using a long poll if (pair.Response != null && pair.Response.Session != null) { qOpts.WaitIndex = pair.LastIndex; continue; } // If the session is null and the lock failed to acquire, then it means a lock-delay is in effect and a timed wait must be used to avoid a hot loop. try { Task.Delay(DefaultLockRetryTime, ct).Wait(ct); } catch (TaskCanceledException) { /* Ignore TaskTaskCanceledException */ } } throw new LockNotHeldException("Unable to acquire the lock with Consul"); } finally { if (ct.IsCancellationRequested || (!IsHeld && !string.IsNullOrEmpty(Opts.Session))) { _cts.Cancel(); if (_sessionRenewTask != null) { try { _sessionRenewTask.Wait(); } catch (AggregateException) { // Ignore AggregateExceptions from the tasks during Release, since if the Renew task died, the developer will be Super Confused if they see the exception during Release. } } else { _client.Session.Destroy(Opts.Session).Wait(); } Opts.Session = null; } } } }
public Task <QueryResult <PreparedQueryDefinition[]> > List(QueryOptions q, CancellationToken ct = default(CancellationToken)) { return(_client.Get <PreparedQueryDefinition[]>("/v1/query", q).Execute(ct)); }
/// <summary> /// Lists the existing ACL Tokens in Consul /// </summary> /// <param name="queryOptions">Customized query options</param> /// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param> /// <returns>A query result containing an array of ACL Tokens</returns> public Task <QueryResult <TokenEntry[]> > List(QueryOptions queryOptions, CancellationToken ct = default(CancellationToken)) { return(_client.Get <TokenEntry[]>("/v1/acl/tokens", queryOptions).Execute(ct)); }
/// <summary> /// monitorLock is a long running routine to monitor a semaphore ownership /// It sets IsHeld to false if we lose our slot. /// </summary> /// <param name="lockSession">The session ID to monitor</param> private Task MonitorLock(string lockSession) { return(Task.Factory.StartNew(async() => { try { var opts = new QueryOptions() { Consistency = ConsistencyMode.Consistent }; _retries = Opts.MonitorRetries; while (IsHeld && !_cts.Token.IsCancellationRequested) { try { var pairs = await _client.KV.List(Opts.Prefix, opts).ConfigureAwait(false); if (pairs.Response != null) { _retries = Opts.MonitorRetries; var lockPair = FindLock(pairs.Response); var semaphoreLock = DecodeLock(lockPair); PruneDeadHolders(semaphoreLock, pairs.Response); // Slot is no longer held! Shut down everything. if (!semaphoreLock.Holders.ContainsKey(lockSession)) { IsHeld = false; DisposeCancellationTokenSource(); return; } // Semaphore is still held, start a blocking query opts.WaitIndex = pairs.LastIndex; continue; } // Failsafe in case the KV store is unavailable else { IsHeld = false; DisposeCancellationTokenSource(); return; } } catch (ConsulRequestException) { if (_retries > 0) { await Task.Delay(Opts.MonitorRetryTime, _cts.Token).ConfigureAwait(false); _retries--; opts.WaitIndex = 0; continue; } throw; } catch (OperationCanceledException) { // Ignore and retry since this could be the underlying HTTPClient being swapped out/disposed of. } catch (Exception) { throw; } } } finally { IsHeld = false; } }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap()); }