|
|
|
|
@@ -20,7 +20,7 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
public event EventHandler<RegionEventArgs>? RegionJoined;
|
|
|
|
|
public event EventHandler<RegionEventArgs>? RegionLeft;
|
|
|
|
|
public event EventHandler<RegionEventArgs>? RegionHealthChanged;
|
|
|
|
|
public event EventHandler<GlobalPromotionEventArgs>? GlobalPromotionRequested;
|
|
|
|
|
public event EventHandler<HubPromotionEventArgs>? GlobalPromotionRequested;
|
|
|
|
|
|
|
|
|
|
public FederationHub(
|
|
|
|
|
IRegionRegistry registry,
|
|
|
|
|
@@ -138,8 +138,8 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initiates a global promotion across all regions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public async Task<GlobalPromotionResult> InitiateGlobalPromotionAsync(
|
|
|
|
|
GlobalPromotionRequest request,
|
|
|
|
|
public async Task<HubPromotionResult> InitiateGlobalPromotionAsync(
|
|
|
|
|
HubPromotionRequest request,
|
|
|
|
|
CancellationToken ct = default)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
|
@@ -153,21 +153,21 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
? _regions.Values.Where(r => request.TargetRegions.Contains(r.RegionId)).ToList()
|
|
|
|
|
: _regions.Values.Where(r => r.Status == RegionStatus.Active).ToList();
|
|
|
|
|
|
|
|
|
|
var promotion = new GlobalPromotion
|
|
|
|
|
var promotion = new HubPromotion
|
|
|
|
|
{
|
|
|
|
|
Id = request.PromotionId,
|
|
|
|
|
ReleaseId = request.ReleaseId,
|
|
|
|
|
ReleaseName = request.ReleaseName,
|
|
|
|
|
Strategy = request.Strategy,
|
|
|
|
|
TargetRegions = targetRegions.Select(r => r.RegionId).ToImmutableArray(),
|
|
|
|
|
Status = GlobalPromotionStatus.InProgress,
|
|
|
|
|
Status = HubPromotionStatus.InProgress,
|
|
|
|
|
StartedAt = _timeProvider.GetUtcNow(),
|
|
|
|
|
RegionStatuses = targetRegions.ToDictionary(
|
|
|
|
|
r => r.RegionId,
|
|
|
|
|
_ => RegionPromotionStatus.Pending).ToImmutableDictionary()
|
|
|
|
|
_ => HubRegionPromotionStatus.Pending).ToImmutableDictionary()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
GlobalPromotionRequested?.Invoke(this, new GlobalPromotionEventArgs
|
|
|
|
|
GlobalPromotionRequested?.Invoke(this, new HubPromotionEventArgs
|
|
|
|
|
{
|
|
|
|
|
Promotion = promotion
|
|
|
|
|
});
|
|
|
|
|
@@ -175,15 +175,15 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
// Execute based on strategy
|
|
|
|
|
var results = request.Strategy switch
|
|
|
|
|
{
|
|
|
|
|
GlobalPromotionStrategy.Parallel => await ExecuteParallelPromotionAsync(promotion, request, ct),
|
|
|
|
|
GlobalPromotionStrategy.Sequential => await ExecuteSequentialPromotionAsync(promotion, request, ct),
|
|
|
|
|
GlobalPromotionStrategy.RollingWave => await ExecuteRollingWavePromotionAsync(promotion, request, ct),
|
|
|
|
|
HubPromotionStrategy.Parallel => await ExecuteParallelPromotionAsync(promotion, request, ct),
|
|
|
|
|
HubPromotionStrategy.Sequential => await ExecuteSequentialPromotionAsync(promotion, request, ct),
|
|
|
|
|
HubPromotionStrategy.RollingWave => await ExecuteRollingWavePromotionAsync(promotion, request, ct),
|
|
|
|
|
_ => await ExecuteSequentialPromotionAsync(promotion, request, ct)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var success = results.All(r => r.Success);
|
|
|
|
|
|
|
|
|
|
return new GlobalPromotionResult
|
|
|
|
|
return new HubPromotionResult
|
|
|
|
|
{
|
|
|
|
|
PromotionId = promotion.Id,
|
|
|
|
|
Success = success,
|
|
|
|
|
@@ -263,9 +263,9 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<RegionPromotionResult>> ExecuteParallelPromotionAsync(
|
|
|
|
|
GlobalPromotion promotion,
|
|
|
|
|
GlobalPromotionRequest request,
|
|
|
|
|
private async Task<List<HubRegionPromotionResult>> ExecuteParallelPromotionAsync(
|
|
|
|
|
HubPromotion promotion,
|
|
|
|
|
HubPromotionRequest request,
|
|
|
|
|
CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
var tasks = promotion.TargetRegions.Select(regionId =>
|
|
|
|
|
@@ -275,12 +275,12 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
return results.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<RegionPromotionResult>> ExecuteSequentialPromotionAsync(
|
|
|
|
|
GlobalPromotion promotion,
|
|
|
|
|
GlobalPromotionRequest request,
|
|
|
|
|
private async Task<List<HubRegionPromotionResult>> ExecuteSequentialPromotionAsync(
|
|
|
|
|
HubPromotion promotion,
|
|
|
|
|
HubPromotionRequest request,
|
|
|
|
|
CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
var results = new List<RegionPromotionResult>();
|
|
|
|
|
var results = new List<HubRegionPromotionResult>();
|
|
|
|
|
|
|
|
|
|
foreach (var regionId in promotion.TargetRegions)
|
|
|
|
|
{
|
|
|
|
|
@@ -296,12 +296,12 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<RegionPromotionResult>> ExecuteRollingWavePromotionAsync(
|
|
|
|
|
GlobalPromotion promotion,
|
|
|
|
|
GlobalPromotionRequest request,
|
|
|
|
|
private async Task<List<HubRegionPromotionResult>> ExecuteRollingWavePromotionAsync(
|
|
|
|
|
HubPromotion promotion,
|
|
|
|
|
HubPromotionRequest request,
|
|
|
|
|
CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
var results = new List<RegionPromotionResult>();
|
|
|
|
|
var results = new List<HubRegionPromotionResult>();
|
|
|
|
|
var waveSize = request.WaveSize ?? 2;
|
|
|
|
|
var waves = promotion.TargetRegions
|
|
|
|
|
.Select((r, i) => (Region: r, Wave: i / waveSize))
|
|
|
|
|
@@ -331,14 +331,14 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<RegionPromotionResult> ExecuteRegionPromotionAsync(
|
|
|
|
|
private async Task<HubRegionPromotionResult> ExecuteRegionPromotionAsync(
|
|
|
|
|
string regionId,
|
|
|
|
|
GlobalPromotionRequest request,
|
|
|
|
|
HubPromotionRequest request,
|
|
|
|
|
CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
if (!_regions.TryGetValue(regionId, out var region))
|
|
|
|
|
{
|
|
|
|
|
return new RegionPromotionResult
|
|
|
|
|
return new HubRegionPromotionResult
|
|
|
|
|
{
|
|
|
|
|
RegionId = regionId,
|
|
|
|
|
Success = false,
|
|
|
|
|
@@ -360,7 +360,7 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
}
|
|
|
|
|
}, ct);
|
|
|
|
|
|
|
|
|
|
return new RegionPromotionResult
|
|
|
|
|
return new HubRegionPromotionResult
|
|
|
|
|
{
|
|
|
|
|
RegionId = regionId,
|
|
|
|
|
Success = true,
|
|
|
|
|
@@ -373,7 +373,7 @@ public sealed class FederationHub : BackgroundService
|
|
|
|
|
"Failed to promote to region {RegionId}",
|
|
|
|
|
regionId);
|
|
|
|
|
|
|
|
|
|
return new RegionPromotionResult
|
|
|
|
|
return new HubRegionPromotionResult
|
|
|
|
|
{
|
|
|
|
|
RegionId = regionId,
|
|
|
|
|
Success = false,
|
|
|
|
|
@@ -483,12 +483,12 @@ public sealed record RegistrationResult
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Request for global promotion.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record GlobalPromotionRequest
|
|
|
|
|
public sealed record HubPromotionRequest
|
|
|
|
|
{
|
|
|
|
|
public required Guid PromotionId { get; init; }
|
|
|
|
|
public required Guid ReleaseId { get; init; }
|
|
|
|
|
public required string ReleaseName { get; init; }
|
|
|
|
|
public GlobalPromotionStrategy Strategy { get; init; } = GlobalPromotionStrategy.Sequential;
|
|
|
|
|
public HubPromotionStrategy Strategy { get; init; } = HubPromotionStrategy.Sequential;
|
|
|
|
|
public ImmutableArray<string> TargetRegions { get; init; } = [];
|
|
|
|
|
public bool StopOnFailure { get; init; } = true;
|
|
|
|
|
public int? WaveSize { get; init; }
|
|
|
|
|
@@ -498,7 +498,7 @@ public sealed record GlobalPromotionRequest
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Global promotion strategy.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public enum GlobalPromotionStrategy
|
|
|
|
|
public enum HubPromotionStrategy
|
|
|
|
|
{
|
|
|
|
|
Sequential,
|
|
|
|
|
Parallel,
|
|
|
|
|
@@ -508,18 +508,18 @@ public enum GlobalPromotionStrategy
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Result of global promotion.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record GlobalPromotionResult
|
|
|
|
|
public sealed record HubPromotionResult
|
|
|
|
|
{
|
|
|
|
|
public required Guid PromotionId { get; init; }
|
|
|
|
|
public required bool Success { get; init; }
|
|
|
|
|
public required ImmutableArray<RegionPromotionResult> RegionResults { get; init; }
|
|
|
|
|
public required ImmutableArray<HubRegionPromotionResult> RegionResults { get; init; }
|
|
|
|
|
public required TimeSpan Duration { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Result for a single region.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record RegionPromotionResult
|
|
|
|
|
public sealed record HubRegionPromotionResult
|
|
|
|
|
{
|
|
|
|
|
public required string RegionId { get; init; }
|
|
|
|
|
public required bool Success { get; init; }
|
|
|
|
|
@@ -543,23 +543,23 @@ public sealed record FederationStatus
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// A global promotion.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record GlobalPromotion
|
|
|
|
|
public sealed record HubPromotion
|
|
|
|
|
{
|
|
|
|
|
public required Guid Id { get; init; }
|
|
|
|
|
public required Guid ReleaseId { get; init; }
|
|
|
|
|
public required string ReleaseName { get; init; }
|
|
|
|
|
public required GlobalPromotionStrategy Strategy { get; init; }
|
|
|
|
|
public required HubPromotionStrategy Strategy { get; init; }
|
|
|
|
|
public required ImmutableArray<string> TargetRegions { get; init; }
|
|
|
|
|
public required GlobalPromotionStatus Status { get; init; }
|
|
|
|
|
public required HubPromotionStatus Status { get; init; }
|
|
|
|
|
public required DateTimeOffset StartedAt { get; init; }
|
|
|
|
|
public DateTimeOffset? CompletedAt { get; init; }
|
|
|
|
|
public required ImmutableDictionary<string, RegionPromotionStatus> RegionStatuses { get; init; }
|
|
|
|
|
public required ImmutableDictionary<string, HubRegionPromotionStatus> RegionStatuses { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Global promotion status.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public enum GlobalPromotionStatus
|
|
|
|
|
public enum HubPromotionStatus
|
|
|
|
|
{
|
|
|
|
|
Pending,
|
|
|
|
|
InProgress,
|
|
|
|
|
@@ -571,7 +571,7 @@ public enum GlobalPromotionStatus
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Region promotion status.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public enum RegionPromotionStatus
|
|
|
|
|
public enum HubRegionPromotionStatus
|
|
|
|
|
{
|
|
|
|
|
Pending,
|
|
|
|
|
InProgress,
|
|
|
|
|
@@ -592,9 +592,9 @@ public sealed class RegionEventArgs : EventArgs
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Event args for global promotion.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class GlobalPromotionEventArgs : EventArgs
|
|
|
|
|
public sealed class HubPromotionEventArgs : EventArgs
|
|
|
|
|
{
|
|
|
|
|
public required GlobalPromotion Promotion { get; init; }
|
|
|
|
|
public required HubPromotion Promotion { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|