up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public interface IScanQueue
|
||||
{
|
||||
ValueTask<QueueEnqueueResult> EnqueueAsync(
|
||||
ScanQueueMessage message,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
ValueTask<IReadOnlyList<IScanQueueLease>> LeaseAsync(
|
||||
QueueLeaseRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
ValueTask<IReadOnlyList<IScanQueueLease>> ClaimExpiredLeasesAsync(
|
||||
QueueClaimOptions options,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public interface IScanQueue
|
||||
{
|
||||
ValueTask<QueueEnqueueResult> EnqueueAsync(
|
||||
ScanQueueMessage message,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
ValueTask<IReadOnlyList<IScanQueueLease>> LeaseAsync(
|
||||
QueueLeaseRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
ValueTask<IReadOnlyList<IScanQueueLease>> ClaimExpiredLeasesAsync(
|
||||
QueueClaimOptions options,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public interface IScanQueueLease
|
||||
{
|
||||
string MessageId { get; }
|
||||
|
||||
string JobId { get; }
|
||||
|
||||
ReadOnlyMemory<byte> Payload { get; }
|
||||
|
||||
int Attempt { get; }
|
||||
|
||||
DateTimeOffset EnqueuedAt { get; }
|
||||
|
||||
DateTimeOffset LeaseExpiresAt { get; }
|
||||
|
||||
string Consumer { get; }
|
||||
|
||||
string? IdempotencyKey { get; }
|
||||
|
||||
string? TraceId { get; }
|
||||
|
||||
IReadOnlyDictionary<string, string> Attributes { get; }
|
||||
|
||||
Task AcknowledgeAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task RenewAsync(TimeSpan leaseDuration, CancellationToken cancellationToken = default);
|
||||
|
||||
Task ReleaseAsync(QueueReleaseDisposition disposition, CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeadLetterAsync(string reason, CancellationToken cancellationToken = default);
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public interface IScanQueueLease
|
||||
{
|
||||
string MessageId { get; }
|
||||
|
||||
string JobId { get; }
|
||||
|
||||
ReadOnlyMemory<byte> Payload { get; }
|
||||
|
||||
int Attempt { get; }
|
||||
|
||||
DateTimeOffset EnqueuedAt { get; }
|
||||
|
||||
DateTimeOffset LeaseExpiresAt { get; }
|
||||
|
||||
string Consumer { get; }
|
||||
|
||||
string? IdempotencyKey { get; }
|
||||
|
||||
string? TraceId { get; }
|
||||
|
||||
IReadOnlyDictionary<string, string> Attributes { get; }
|
||||
|
||||
Task AcknowledgeAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task RenewAsync(TimeSpan leaseDuration, CancellationToken cancellationToken = default);
|
||||
|
||||
Task ReleaseAsync(QueueReleaseDisposition disposition, CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeadLetterAsync(string reason, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,82 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NATS.Client.JetStream;
|
||||
|
||||
namespace StellaOps.Scanner.Queue.Nats;
|
||||
|
||||
internal sealed class NatsScanQueueLease : IScanQueueLease
|
||||
{
|
||||
private readonly NatsScanQueue _queue;
|
||||
private readonly NatsJSMsg<byte[]> _message;
|
||||
private int _completed;
|
||||
|
||||
internal NatsScanQueueLease(
|
||||
NatsScanQueue queue,
|
||||
NatsJSMsg<byte[]> message,
|
||||
string messageId,
|
||||
string jobId,
|
||||
byte[] payload,
|
||||
int attempt,
|
||||
DateTimeOffset enqueuedAt,
|
||||
DateTimeOffset leaseExpiresAt,
|
||||
string consumer,
|
||||
string? idempotencyKey,
|
||||
string? traceId,
|
||||
IReadOnlyDictionary<string, string> attributes)
|
||||
{
|
||||
_queue = queue;
|
||||
_message = message;
|
||||
MessageId = messageId;
|
||||
JobId = jobId;
|
||||
Payload = payload;
|
||||
Attempt = attempt;
|
||||
EnqueuedAt = enqueuedAt;
|
||||
LeaseExpiresAt = leaseExpiresAt;
|
||||
Consumer = consumer;
|
||||
IdempotencyKey = idempotencyKey;
|
||||
TraceId = traceId;
|
||||
Attributes = attributes;
|
||||
}
|
||||
|
||||
public string MessageId { get; }
|
||||
|
||||
public string JobId { get; }
|
||||
|
||||
public ReadOnlyMemory<byte> Payload { get; }
|
||||
|
||||
public int Attempt { get; internal set; }
|
||||
|
||||
public DateTimeOffset EnqueuedAt { get; }
|
||||
|
||||
public DateTimeOffset LeaseExpiresAt { get; private set; }
|
||||
|
||||
public string Consumer { get; }
|
||||
|
||||
public string? IdempotencyKey { get; }
|
||||
|
||||
public string? TraceId { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, string> Attributes { get; }
|
||||
|
||||
internal NatsJSMsg<byte[]> Message => _message;
|
||||
|
||||
public Task AcknowledgeAsync(CancellationToken cancellationToken = default)
|
||||
=> _queue.AcknowledgeAsync(this, cancellationToken);
|
||||
|
||||
public Task RenewAsync(TimeSpan leaseDuration, CancellationToken cancellationToken = default)
|
||||
=> _queue.RenewLeaseAsync(this, leaseDuration, cancellationToken);
|
||||
|
||||
public Task ReleaseAsync(QueueReleaseDisposition disposition, CancellationToken cancellationToken = default)
|
||||
=> _queue.ReleaseAsync(this, disposition, cancellationToken);
|
||||
|
||||
public Task DeadLetterAsync(string reason, CancellationToken cancellationToken = default)
|
||||
=> _queue.DeadLetterAsync(this, reason, cancellationToken);
|
||||
|
||||
internal bool TryBeginCompletion()
|
||||
=> Interlocked.CompareExchange(ref _completed, 1, 0) == 0;
|
||||
|
||||
internal void RefreshLease(DateTimeOffset expiresAt)
|
||||
=> LeaseExpiresAt = expiresAt;
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NATS.Client.JetStream;
|
||||
|
||||
namespace StellaOps.Scanner.Queue.Nats;
|
||||
|
||||
internal sealed class NatsScanQueueLease : IScanQueueLease
|
||||
{
|
||||
private readonly NatsScanQueue _queue;
|
||||
private readonly NatsJSMsg<byte[]> _message;
|
||||
private int _completed;
|
||||
|
||||
internal NatsScanQueueLease(
|
||||
NatsScanQueue queue,
|
||||
NatsJSMsg<byte[]> message,
|
||||
string messageId,
|
||||
string jobId,
|
||||
byte[] payload,
|
||||
int attempt,
|
||||
DateTimeOffset enqueuedAt,
|
||||
DateTimeOffset leaseExpiresAt,
|
||||
string consumer,
|
||||
string? idempotencyKey,
|
||||
string? traceId,
|
||||
IReadOnlyDictionary<string, string> attributes)
|
||||
{
|
||||
_queue = queue;
|
||||
_message = message;
|
||||
MessageId = messageId;
|
||||
JobId = jobId;
|
||||
Payload = payload;
|
||||
Attempt = attempt;
|
||||
EnqueuedAt = enqueuedAt;
|
||||
LeaseExpiresAt = leaseExpiresAt;
|
||||
Consumer = consumer;
|
||||
IdempotencyKey = idempotencyKey;
|
||||
TraceId = traceId;
|
||||
Attributes = attributes;
|
||||
}
|
||||
|
||||
public string MessageId { get; }
|
||||
|
||||
public string JobId { get; }
|
||||
|
||||
public ReadOnlyMemory<byte> Payload { get; }
|
||||
|
||||
public int Attempt { get; internal set; }
|
||||
|
||||
public DateTimeOffset EnqueuedAt { get; }
|
||||
|
||||
public DateTimeOffset LeaseExpiresAt { get; private set; }
|
||||
|
||||
public string Consumer { get; }
|
||||
|
||||
public string? IdempotencyKey { get; }
|
||||
|
||||
public string? TraceId { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, string> Attributes { get; }
|
||||
|
||||
internal NatsJSMsg<byte[]> Message => _message;
|
||||
|
||||
public Task AcknowledgeAsync(CancellationToken cancellationToken = default)
|
||||
=> _queue.AcknowledgeAsync(this, cancellationToken);
|
||||
|
||||
public Task RenewAsync(TimeSpan leaseDuration, CancellationToken cancellationToken = default)
|
||||
=> _queue.RenewLeaseAsync(this, leaseDuration, cancellationToken);
|
||||
|
||||
public Task ReleaseAsync(QueueReleaseDisposition disposition, CancellationToken cancellationToken = default)
|
||||
=> _queue.ReleaseAsync(this, disposition, cancellationToken);
|
||||
|
||||
public Task DeadLetterAsync(string reason, CancellationToken cancellationToken = default)
|
||||
=> _queue.DeadLetterAsync(this, reason, cancellationToken);
|
||||
|
||||
internal bool TryBeginCompletion()
|
||||
=> Interlocked.CompareExchange(ref _completed, 1, 0) == 0;
|
||||
|
||||
internal void RefreshLease(DateTimeOffset expiresAt)
|
||||
=> LeaseExpiresAt = expiresAt;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
internal static class QueueEnvelopeFields
|
||||
{
|
||||
public const string Payload = "payload";
|
||||
public const string JobId = "jobId";
|
||||
public const string IdempotencyKey = "idempotency";
|
||||
public const string Attempt = "attempt";
|
||||
public const string EnqueuedAt = "enqueuedAt";
|
||||
public const string TraceId = "traceId";
|
||||
public const string AttributePrefix = "attr:";
|
||||
}
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
internal static class QueueEnvelopeFields
|
||||
{
|
||||
public const string Payload = "payload";
|
||||
public const string JobId = "jobId";
|
||||
public const string IdempotencyKey = "idempotency";
|
||||
public const string Attempt = "attempt";
|
||||
public const string EnqueuedAt = "enqueuedAt";
|
||||
public const string TraceId = "traceId";
|
||||
public const string AttributePrefix = "attr:";
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
internal static class QueueMetrics
|
||||
{
|
||||
private const string TransportTagName = "transport";
|
||||
|
||||
private static readonly Meter Meter = new("StellaOps.Scanner.Queue");
|
||||
private static readonly Counter<long> EnqueuedCounter = Meter.CreateCounter<long>("scanner_queue_enqueued_total");
|
||||
private static readonly Counter<long> DeduplicatedCounter = Meter.CreateCounter<long>("scanner_queue_deduplicated_total");
|
||||
private static readonly Counter<long> AckCounter = Meter.CreateCounter<long>("scanner_queue_ack_total");
|
||||
private static readonly Counter<long> RetryCounter = Meter.CreateCounter<long>("scanner_queue_retry_total");
|
||||
private static readonly Counter<long> DeadLetterCounter = Meter.CreateCounter<long>("scanner_queue_deadletter_total");
|
||||
|
||||
public static void RecordEnqueued(string transport) => EnqueuedCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordDeduplicated(string transport) => DeduplicatedCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordAck(string transport) => AckCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordRetry(string transport) => RetryCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordDeadLetter(string transport) => DeadLetterCounter.Add(1, BuildTags(transport));
|
||||
|
||||
private static KeyValuePair<string, object?>[] BuildTags(string transport)
|
||||
=> new[] { new KeyValuePair<string, object?>(TransportTagName, transport) };
|
||||
}
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
internal static class QueueMetrics
|
||||
{
|
||||
private const string TransportTagName = "transport";
|
||||
|
||||
private static readonly Meter Meter = new("StellaOps.Scanner.Queue");
|
||||
private static readonly Counter<long> EnqueuedCounter = Meter.CreateCounter<long>("scanner_queue_enqueued_total");
|
||||
private static readonly Counter<long> DeduplicatedCounter = Meter.CreateCounter<long>("scanner_queue_deduplicated_total");
|
||||
private static readonly Counter<long> AckCounter = Meter.CreateCounter<long>("scanner_queue_ack_total");
|
||||
private static readonly Counter<long> RetryCounter = Meter.CreateCounter<long>("scanner_queue_retry_total");
|
||||
private static readonly Counter<long> DeadLetterCounter = Meter.CreateCounter<long>("scanner_queue_deadletter_total");
|
||||
|
||||
public static void RecordEnqueued(string transport) => EnqueuedCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordDeduplicated(string transport) => DeduplicatedCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordAck(string transport) => AckCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordRetry(string transport) => RetryCounter.Add(1, BuildTags(transport));
|
||||
|
||||
public static void RecordDeadLetter(string transport) => DeadLetterCounter.Add(1, BuildTags(transport));
|
||||
|
||||
private static KeyValuePair<string, object?>[] BuildTags(string transport)
|
||||
=> new[] { new KeyValuePair<string, object?>(TransportTagName, transport) };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public enum QueueTransportKind
|
||||
{
|
||||
Redis,
|
||||
Nats
|
||||
}
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public enum QueueTransportKind
|
||||
{
|
||||
Redis,
|
||||
Nats
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,76 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Queue.Redis;
|
||||
|
||||
internal sealed class RedisScanQueueLease : IScanQueueLease
|
||||
{
|
||||
private readonly RedisScanQueue _queue;
|
||||
private int _completed;
|
||||
|
||||
internal RedisScanQueueLease(
|
||||
RedisScanQueue queue,
|
||||
string messageId,
|
||||
string jobId,
|
||||
byte[] payload,
|
||||
int attempt,
|
||||
DateTimeOffset enqueuedAt,
|
||||
DateTimeOffset leaseExpiresAt,
|
||||
string consumer,
|
||||
string? idempotencyKey,
|
||||
string? traceId,
|
||||
IReadOnlyDictionary<string, string> attributes)
|
||||
{
|
||||
_queue = queue;
|
||||
MessageId = messageId;
|
||||
JobId = jobId;
|
||||
Payload = payload;
|
||||
Attempt = attempt;
|
||||
EnqueuedAt = enqueuedAt;
|
||||
LeaseExpiresAt = leaseExpiresAt;
|
||||
Consumer = consumer;
|
||||
IdempotencyKey = idempotencyKey;
|
||||
TraceId = traceId;
|
||||
Attributes = attributes;
|
||||
}
|
||||
|
||||
public string MessageId { get; }
|
||||
|
||||
public string JobId { get; }
|
||||
|
||||
public ReadOnlyMemory<byte> Payload { get; }
|
||||
|
||||
public int Attempt { get; }
|
||||
|
||||
public DateTimeOffset EnqueuedAt { get; }
|
||||
|
||||
public DateTimeOffset LeaseExpiresAt { get; private set; }
|
||||
|
||||
public string Consumer { get; }
|
||||
|
||||
public string? IdempotencyKey { get; }
|
||||
|
||||
public string? TraceId { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, string> Attributes { get; }
|
||||
|
||||
public Task AcknowledgeAsync(CancellationToken cancellationToken = default)
|
||||
=> _queue.AcknowledgeAsync(this, cancellationToken);
|
||||
|
||||
public Task RenewAsync(TimeSpan leaseDuration, CancellationToken cancellationToken = default)
|
||||
=> _queue.RenewLeaseAsync(this, leaseDuration, cancellationToken);
|
||||
|
||||
public Task ReleaseAsync(QueueReleaseDisposition disposition, CancellationToken cancellationToken = default)
|
||||
=> _queue.ReleaseAsync(this, disposition, cancellationToken);
|
||||
|
||||
public Task DeadLetterAsync(string reason, CancellationToken cancellationToken = default)
|
||||
=> _queue.DeadLetterAsync(this, reason, cancellationToken);
|
||||
|
||||
internal bool TryBeginCompletion()
|
||||
=> Interlocked.CompareExchange(ref _completed, 1, 0) == 0;
|
||||
|
||||
internal void RefreshLease(DateTimeOffset expiresAt)
|
||||
=> LeaseExpiresAt = expiresAt;
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Queue.Redis;
|
||||
|
||||
internal sealed class RedisScanQueueLease : IScanQueueLease
|
||||
{
|
||||
private readonly RedisScanQueue _queue;
|
||||
private int _completed;
|
||||
|
||||
internal RedisScanQueueLease(
|
||||
RedisScanQueue queue,
|
||||
string messageId,
|
||||
string jobId,
|
||||
byte[] payload,
|
||||
int attempt,
|
||||
DateTimeOffset enqueuedAt,
|
||||
DateTimeOffset leaseExpiresAt,
|
||||
string consumer,
|
||||
string? idempotencyKey,
|
||||
string? traceId,
|
||||
IReadOnlyDictionary<string, string> attributes)
|
||||
{
|
||||
_queue = queue;
|
||||
MessageId = messageId;
|
||||
JobId = jobId;
|
||||
Payload = payload;
|
||||
Attempt = attempt;
|
||||
EnqueuedAt = enqueuedAt;
|
||||
LeaseExpiresAt = leaseExpiresAt;
|
||||
Consumer = consumer;
|
||||
IdempotencyKey = idempotencyKey;
|
||||
TraceId = traceId;
|
||||
Attributes = attributes;
|
||||
}
|
||||
|
||||
public string MessageId { get; }
|
||||
|
||||
public string JobId { get; }
|
||||
|
||||
public ReadOnlyMemory<byte> Payload { get; }
|
||||
|
||||
public int Attempt { get; }
|
||||
|
||||
public DateTimeOffset EnqueuedAt { get; }
|
||||
|
||||
public DateTimeOffset LeaseExpiresAt { get; private set; }
|
||||
|
||||
public string Consumer { get; }
|
||||
|
||||
public string? IdempotencyKey { get; }
|
||||
|
||||
public string? TraceId { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, string> Attributes { get; }
|
||||
|
||||
public Task AcknowledgeAsync(CancellationToken cancellationToken = default)
|
||||
=> _queue.AcknowledgeAsync(this, cancellationToken);
|
||||
|
||||
public Task RenewAsync(TimeSpan leaseDuration, CancellationToken cancellationToken = default)
|
||||
=> _queue.RenewLeaseAsync(this, leaseDuration, cancellationToken);
|
||||
|
||||
public Task ReleaseAsync(QueueReleaseDisposition disposition, CancellationToken cancellationToken = default)
|
||||
=> _queue.ReleaseAsync(this, disposition, cancellationToken);
|
||||
|
||||
public Task DeadLetterAsync(string reason, CancellationToken cancellationToken = default)
|
||||
=> _queue.DeadLetterAsync(this, reason, cancellationToken);
|
||||
|
||||
internal bool TryBeginCompletion()
|
||||
=> Interlocked.CompareExchange(ref _completed, 1, 0) == 0;
|
||||
|
||||
internal void RefreshLease(DateTimeOffset expiresAt)
|
||||
=> LeaseExpiresAt = expiresAt;
|
||||
}
|
||||
|
||||
@@ -1,115 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public sealed class ScanQueueMessage
|
||||
{
|
||||
private readonly byte[] _payload;
|
||||
|
||||
public ScanQueueMessage(string jobId, ReadOnlyMemory<byte> payload)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(jobId))
|
||||
{
|
||||
throw new ArgumentException("Job identifier must be provided.", nameof(jobId));
|
||||
}
|
||||
|
||||
JobId = jobId;
|
||||
_payload = CopyPayload(payload);
|
||||
}
|
||||
|
||||
public string JobId { get; }
|
||||
|
||||
public string? IdempotencyKey { get; init; }
|
||||
|
||||
public string? TraceId { get; init; }
|
||||
|
||||
public IReadOnlyDictionary<string, string>? Attributes { get; init; }
|
||||
|
||||
public ReadOnlyMemory<byte> Payload => _payload;
|
||||
|
||||
private static byte[] CopyPayload(ReadOnlyMemory<byte> payload)
|
||||
{
|
||||
if (payload.Length == 0)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var copy = new byte[payload.Length];
|
||||
payload.Span.CopyTo(copy);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct QueueEnqueueResult(string MessageId, bool Deduplicated);
|
||||
|
||||
public sealed class QueueLeaseRequest
|
||||
{
|
||||
public QueueLeaseRequest(string consumer, int batchSize, TimeSpan leaseDuration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(consumer))
|
||||
{
|
||||
throw new ArgumentException("Consumer name must be provided.", nameof(consumer));
|
||||
}
|
||||
|
||||
if (batchSize <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(batchSize), batchSize, "Batch size must be positive.");
|
||||
}
|
||||
|
||||
if (leaseDuration <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(leaseDuration), leaseDuration, "Lease duration must be positive.");
|
||||
}
|
||||
|
||||
Consumer = consumer;
|
||||
BatchSize = batchSize;
|
||||
LeaseDuration = leaseDuration;
|
||||
}
|
||||
|
||||
public string Consumer { get; }
|
||||
|
||||
public int BatchSize { get; }
|
||||
|
||||
public TimeSpan LeaseDuration { get; }
|
||||
}
|
||||
|
||||
public sealed class QueueClaimOptions
|
||||
{
|
||||
public QueueClaimOptions(
|
||||
string claimantConsumer,
|
||||
int batchSize,
|
||||
TimeSpan minIdleTime)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(claimantConsumer))
|
||||
{
|
||||
throw new ArgumentException("Consumer must be provided.", nameof(claimantConsumer));
|
||||
}
|
||||
|
||||
if (batchSize <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(batchSize), batchSize, "Batch size must be positive.");
|
||||
}
|
||||
|
||||
if (minIdleTime < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(minIdleTime), minIdleTime, "Idle time cannot be negative.");
|
||||
}
|
||||
|
||||
ClaimantConsumer = claimantConsumer;
|
||||
BatchSize = batchSize;
|
||||
MinIdleTime = minIdleTime;
|
||||
}
|
||||
|
||||
public string ClaimantConsumer { get; }
|
||||
|
||||
public int BatchSize { get; }
|
||||
|
||||
public TimeSpan MinIdleTime { get; }
|
||||
}
|
||||
|
||||
public enum QueueReleaseDisposition
|
||||
{
|
||||
Retry,
|
||||
Abandon
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public sealed class ScanQueueMessage
|
||||
{
|
||||
private readonly byte[] _payload;
|
||||
|
||||
public ScanQueueMessage(string jobId, ReadOnlyMemory<byte> payload)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(jobId))
|
||||
{
|
||||
throw new ArgumentException("Job identifier must be provided.", nameof(jobId));
|
||||
}
|
||||
|
||||
JobId = jobId;
|
||||
_payload = CopyPayload(payload);
|
||||
}
|
||||
|
||||
public string JobId { get; }
|
||||
|
||||
public string? IdempotencyKey { get; init; }
|
||||
|
||||
public string? TraceId { get; init; }
|
||||
|
||||
public IReadOnlyDictionary<string, string>? Attributes { get; init; }
|
||||
|
||||
public ReadOnlyMemory<byte> Payload => _payload;
|
||||
|
||||
private static byte[] CopyPayload(ReadOnlyMemory<byte> payload)
|
||||
{
|
||||
if (payload.Length == 0)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var copy = new byte[payload.Length];
|
||||
payload.Span.CopyTo(copy);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct QueueEnqueueResult(string MessageId, bool Deduplicated);
|
||||
|
||||
public sealed class QueueLeaseRequest
|
||||
{
|
||||
public QueueLeaseRequest(string consumer, int batchSize, TimeSpan leaseDuration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(consumer))
|
||||
{
|
||||
throw new ArgumentException("Consumer name must be provided.", nameof(consumer));
|
||||
}
|
||||
|
||||
if (batchSize <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(batchSize), batchSize, "Batch size must be positive.");
|
||||
}
|
||||
|
||||
if (leaseDuration <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(leaseDuration), leaseDuration, "Lease duration must be positive.");
|
||||
}
|
||||
|
||||
Consumer = consumer;
|
||||
BatchSize = batchSize;
|
||||
LeaseDuration = leaseDuration;
|
||||
}
|
||||
|
||||
public string Consumer { get; }
|
||||
|
||||
public int BatchSize { get; }
|
||||
|
||||
public TimeSpan LeaseDuration { get; }
|
||||
}
|
||||
|
||||
public sealed class QueueClaimOptions
|
||||
{
|
||||
public QueueClaimOptions(
|
||||
string claimantConsumer,
|
||||
int batchSize,
|
||||
TimeSpan minIdleTime)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(claimantConsumer))
|
||||
{
|
||||
throw new ArgumentException("Consumer must be provided.", nameof(claimantConsumer));
|
||||
}
|
||||
|
||||
if (batchSize <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(batchSize), batchSize, "Batch size must be positive.");
|
||||
}
|
||||
|
||||
if (minIdleTime < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(minIdleTime), minIdleTime, "Idle time cannot be negative.");
|
||||
}
|
||||
|
||||
ClaimantConsumer = claimantConsumer;
|
||||
BatchSize = batchSize;
|
||||
MinIdleTime = minIdleTime;
|
||||
}
|
||||
|
||||
public string ClaimantConsumer { get; }
|
||||
|
||||
public int BatchSize { get; }
|
||||
|
||||
public TimeSpan MinIdleTime { get; }
|
||||
}
|
||||
|
||||
public enum QueueReleaseDisposition
|
||||
{
|
||||
Retry,
|
||||
Abandon
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.Queue.Nats;
|
||||
using StellaOps.Scanner.Queue.Redis;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public sealed class ScannerQueueHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly IScanQueue _queue;
|
||||
private readonly ILogger<ScannerQueueHealthCheck> _logger;
|
||||
|
||||
public ScannerQueueHealthCheck(
|
||||
IScanQueue queue,
|
||||
ILogger<ScannerQueueHealthCheck> logger)
|
||||
{
|
||||
_queue = queue ?? throw new ArgumentNullException(nameof(queue));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
switch (_queue)
|
||||
{
|
||||
case RedisScanQueue redisQueue:
|
||||
await redisQueue.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
return HealthCheckResult.Healthy("Redis queue reachable.");
|
||||
|
||||
case NatsScanQueue natsQueue:
|
||||
await natsQueue.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
return HealthCheckResult.Healthy("NATS queue reachable.");
|
||||
|
||||
default:
|
||||
return HealthCheckResult.Healthy("Queue transport without dedicated ping returned healthy.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Scanner queue health check failed.");
|
||||
return new HealthCheckResult(
|
||||
context.Registration.FailureStatus,
|
||||
"Queue transport unreachable.",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.Queue.Nats;
|
||||
using StellaOps.Scanner.Queue.Redis;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public sealed class ScannerQueueHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly IScanQueue _queue;
|
||||
private readonly ILogger<ScannerQueueHealthCheck> _logger;
|
||||
|
||||
public ScannerQueueHealthCheck(
|
||||
IScanQueue queue,
|
||||
ILogger<ScannerQueueHealthCheck> logger)
|
||||
{
|
||||
_queue = queue ?? throw new ArgumentNullException(nameof(queue));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
switch (_queue)
|
||||
{
|
||||
case RedisScanQueue redisQueue:
|
||||
await redisQueue.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
return HealthCheckResult.Healthy("Redis queue reachable.");
|
||||
|
||||
case NatsScanQueue natsQueue:
|
||||
await natsQueue.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
return HealthCheckResult.Healthy("NATS queue reachable.");
|
||||
|
||||
default:
|
||||
return HealthCheckResult.Healthy("Queue transport without dedicated ping returned healthy.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Scanner queue health check failed.");
|
||||
return new HealthCheckResult(
|
||||
context.Registration.FailureStatus,
|
||||
"Queue transport unreachable.",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public sealed class ScannerQueueOptions
|
||||
{
|
||||
public QueueTransportKind Kind { get; set; } = QueueTransportKind.Redis;
|
||||
|
||||
public RedisQueueOptions Redis { get; set; } = new();
|
||||
|
||||
public NatsQueueOptions Nats { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Default lease duration applied when callers do not override the visibility timeout.
|
||||
/// </summary>
|
||||
public TimeSpan DefaultLeaseDuration { get; set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of times a message may be delivered before it is shunted to the dead-letter queue.
|
||||
/// </summary>
|
||||
public int MaxDeliveryAttempts { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Options controlling retry/backoff/dead-letter handling.
|
||||
/// </summary>
|
||||
public DeadLetterQueueOptions DeadLetter { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initial backoff applied when a job is retried after failure.
|
||||
/// </summary>
|
||||
public TimeSpan RetryInitialBackoff { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum backoff window applied for exponential retry.
|
||||
/// </summary>
|
||||
public TimeSpan RetryMaxBackoff { get; set; } = TimeSpan.FromMinutes(2);
|
||||
}
|
||||
|
||||
public sealed class RedisQueueOptions
|
||||
{
|
||||
public string? ConnectionString { get; set; }
|
||||
|
||||
public int? Database { get; set; }
|
||||
|
||||
public string StreamName { get; set; } = "scanner:jobs";
|
||||
|
||||
public string ConsumerGroup { get; set; } = "scanner-workers";
|
||||
|
||||
public string IdempotencyKeyPrefix { get; set; } = "scanner:jobs:idemp:";
|
||||
|
||||
public TimeSpan IdempotencyWindow { get; set; } = TimeSpan.FromHours(12);
|
||||
|
||||
public int? ApproximateMaxLength { get; set; }
|
||||
|
||||
public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
public TimeSpan ClaimIdleThreshold { get; set; } = TimeSpan.FromMinutes(10);
|
||||
|
||||
public TimeSpan PendingScanWindow { get; set; } = TimeSpan.FromMinutes(30);
|
||||
|
||||
public TimeSpan RetryInitialBackoff { get; set; } = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
public sealed class NatsQueueOptions
|
||||
{
|
||||
public string? Url { get; set; }
|
||||
|
||||
public string Stream { get; set; } = "SCANNER_JOBS";
|
||||
|
||||
public string Subject { get; set; } = "scanner.jobs";
|
||||
|
||||
public string DurableConsumer { get; set; } = "scanner-workers";
|
||||
|
||||
public int MaxInFlight { get; set; } = 64;
|
||||
|
||||
public TimeSpan AckWait { get; set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
public string DeadLetterStream { get; set; } = "SCANNER_JOBS_DEAD";
|
||||
|
||||
public string DeadLetterSubject { get; set; } = "scanner.jobs.dead";
|
||||
|
||||
public TimeSpan RetryDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
public TimeSpan IdleHeartbeat { get; set; } = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
public sealed class DeadLetterQueueOptions
|
||||
{
|
||||
public string StreamName { get; set; } = "scanner:jobs:dead";
|
||||
|
||||
public TimeSpan Retention { get; set; } = TimeSpan.FromDays(7);
|
||||
}
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public sealed class ScannerQueueOptions
|
||||
{
|
||||
public QueueTransportKind Kind { get; set; } = QueueTransportKind.Redis;
|
||||
|
||||
public RedisQueueOptions Redis { get; set; } = new();
|
||||
|
||||
public NatsQueueOptions Nats { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Default lease duration applied when callers do not override the visibility timeout.
|
||||
/// </summary>
|
||||
public TimeSpan DefaultLeaseDuration { get; set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of times a message may be delivered before it is shunted to the dead-letter queue.
|
||||
/// </summary>
|
||||
public int MaxDeliveryAttempts { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Options controlling retry/backoff/dead-letter handling.
|
||||
/// </summary>
|
||||
public DeadLetterQueueOptions DeadLetter { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initial backoff applied when a job is retried after failure.
|
||||
/// </summary>
|
||||
public TimeSpan RetryInitialBackoff { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum backoff window applied for exponential retry.
|
||||
/// </summary>
|
||||
public TimeSpan RetryMaxBackoff { get; set; } = TimeSpan.FromMinutes(2);
|
||||
}
|
||||
|
||||
public sealed class RedisQueueOptions
|
||||
{
|
||||
public string? ConnectionString { get; set; }
|
||||
|
||||
public int? Database { get; set; }
|
||||
|
||||
public string StreamName { get; set; } = "scanner:jobs";
|
||||
|
||||
public string ConsumerGroup { get; set; } = "scanner-workers";
|
||||
|
||||
public string IdempotencyKeyPrefix { get; set; } = "scanner:jobs:idemp:";
|
||||
|
||||
public TimeSpan IdempotencyWindow { get; set; } = TimeSpan.FromHours(12);
|
||||
|
||||
public int? ApproximateMaxLength { get; set; }
|
||||
|
||||
public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
public TimeSpan ClaimIdleThreshold { get; set; } = TimeSpan.FromMinutes(10);
|
||||
|
||||
public TimeSpan PendingScanWindow { get; set; } = TimeSpan.FromMinutes(30);
|
||||
|
||||
public TimeSpan RetryInitialBackoff { get; set; } = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
public sealed class NatsQueueOptions
|
||||
{
|
||||
public string? Url { get; set; }
|
||||
|
||||
public string Stream { get; set; } = "SCANNER_JOBS";
|
||||
|
||||
public string Subject { get; set; } = "scanner.jobs";
|
||||
|
||||
public string DurableConsumer { get; set; } = "scanner-workers";
|
||||
|
||||
public int MaxInFlight { get; set; } = 64;
|
||||
|
||||
public TimeSpan AckWait { get; set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
public string DeadLetterStream { get; set; } = "SCANNER_JOBS_DEAD";
|
||||
|
||||
public string DeadLetterSubject { get; set; } = "scanner.jobs.dead";
|
||||
|
||||
public TimeSpan RetryDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
public TimeSpan IdleHeartbeat { get; set; } = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
public sealed class DeadLetterQueueOptions
|
||||
{
|
||||
public string StreamName { get; set; } = "scanner:jobs:dead";
|
||||
|
||||
public TimeSpan Retention { get; set; } = TimeSpan.FromDays(7);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.Queue.Nats;
|
||||
using StellaOps.Scanner.Queue.Redis;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public static class ScannerQueueServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddScannerQueue(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string sectionName = "scanner:queue")
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
var options = new ScannerQueueOptions();
|
||||
configuration.GetSection(sectionName).Bind(options);
|
||||
|
||||
services.TryAddSingleton(TimeProvider.System);
|
||||
services.AddSingleton(options);
|
||||
|
||||
services.AddSingleton<IScanQueue>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
||||
var timeProvider = sp.GetService<TimeProvider>() ?? TimeProvider.System;
|
||||
|
||||
return options.Kind switch
|
||||
{
|
||||
QueueTransportKind.Redis => new RedisScanQueue(
|
||||
options,
|
||||
options.Redis,
|
||||
loggerFactory.CreateLogger<RedisScanQueue>(),
|
||||
timeProvider),
|
||||
QueueTransportKind.Nats => new NatsScanQueue(
|
||||
options,
|
||||
options.Nats,
|
||||
loggerFactory.CreateLogger<NatsScanQueue>(),
|
||||
timeProvider),
|
||||
_ => throw new InvalidOperationException($"Unsupported queue transport kind '{options.Kind}'.")
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<ScannerQueueHealthCheck>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IHealthChecksBuilder AddScannerQueueHealthCheck(
|
||||
this IHealthChecksBuilder builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
builder.Services.TryAddSingleton<ScannerQueueHealthCheck>();
|
||||
builder.AddCheck<ScannerQueueHealthCheck>(
|
||||
name: "scanner-queue",
|
||||
failureStatus: HealthStatus.Unhealthy,
|
||||
tags: new[] { "scanner", "queue" });
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.Queue.Nats;
|
||||
using StellaOps.Scanner.Queue.Redis;
|
||||
|
||||
namespace StellaOps.Scanner.Queue;
|
||||
|
||||
public static class ScannerQueueServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddScannerQueue(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string sectionName = "scanner:queue")
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
var options = new ScannerQueueOptions();
|
||||
configuration.GetSection(sectionName).Bind(options);
|
||||
|
||||
services.TryAddSingleton(TimeProvider.System);
|
||||
services.AddSingleton(options);
|
||||
|
||||
services.AddSingleton<IScanQueue>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
||||
var timeProvider = sp.GetService<TimeProvider>() ?? TimeProvider.System;
|
||||
|
||||
return options.Kind switch
|
||||
{
|
||||
QueueTransportKind.Redis => new RedisScanQueue(
|
||||
options,
|
||||
options.Redis,
|
||||
loggerFactory.CreateLogger<RedisScanQueue>(),
|
||||
timeProvider),
|
||||
QueueTransportKind.Nats => new NatsScanQueue(
|
||||
options,
|
||||
options.Nats,
|
||||
loggerFactory.CreateLogger<NatsScanQueue>(),
|
||||
timeProvider),
|
||||
_ => throw new InvalidOperationException($"Unsupported queue transport kind '{options.Kind}'.")
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<ScannerQueueHealthCheck>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IHealthChecksBuilder AddScannerQueueHealthCheck(
|
||||
this IHealthChecksBuilder builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
builder.Services.TryAddSingleton<ScannerQueueHealthCheck>();
|
||||
builder.AddCheck<ScannerQueueHealthCheck>(
|
||||
name: "scanner-queue",
|
||||
failureStatus: HealthStatus.Unhealthy,
|
||||
tags: new[] { "scanner", "queue" });
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user