Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Reflection;
using Tilework.Core.Attributes;
using Tilework.LoadBalancing.Enums;
using Tilework.Monitoring.Models;

namespace Tilework.LoadBalancing.Models;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Reflection;
using Tilework.Core.Attributes;
using Tilework.Monitoring.Models;
using Tilework.LoadBalancing.Enums;

namespace Tilework.LoadBalancing.Models;
Expand Down
2 changes: 2 additions & 0 deletions tilework.core/Models/Monitoring/BaseMonitorData.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
namespace Tilework.Monitoring.Models;

public class BaseMonitorData
{
public DateTimeOffset Timestamp { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
using Tilework.Core.Interfaces;
using Tilework.Core.Enums;
using Tilework.Core.Models;
using Tilework.Core.Services;

using Tilework.CertificateManagement.Interfaces;
using Tilework.CertificateManagement.Enums;
using Tilework.Monitoring.Enums;
using Tilework.Monitoring.Models;
using Tilework.Persistence.LoadBalancing.Models;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

Expand All @@ -9,8 +8,6 @@
using AutoMapper;
using InfluxDB.Client;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using InfluxDB.Client.Writes;

using Tilework.Core.Interfaces;
using Tilework.Core.Models;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Tilework.Core.Interfaces;
using Tilework.Core.Models;
using Tilework.Core.Enums;
using Tilework.Core.Services;
using Tilework.Monitoring.Interfaces;
using Tilework.Monitoring.Models;
using Tilework.Monitoring.Enums;
Expand Down
2 changes: 1 addition & 1 deletion tilework.core/Providers/Shared/BaseContainerProvider.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Microsoft.Extensions.Logging;
using System.Linq;


using Tilework.Core.Interfaces;
using Tilework.Core.Models;
using Tilework.Core.Enums;

namespace Tilework.Core.Services;

public abstract class BaseContainerProvider
{
Expand Down
1 change: 0 additions & 1 deletion tilework.core/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ public static IServiceCollection AddLoadBalancing(this IServiceCollection servic

services.AddScoped<ILoadBalancerService, LoadBalancerService>();
services.AddScoped<HAProxyConfigurator>();
services.AddScoped<HAProxyMonitor>();

services.AddHostedService<LoadBalancingInitializer>();

Expand Down
255 changes: 127 additions & 128 deletions tilework.core/Services/LoadBalancing/LoadBalancerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
using Tilework.Persistence.LoadBalancing.Models;
using Tilework.LoadBalancing.Haproxy;

using Tilework.CertificateManagement.Interfaces;
using Tilework.CertificateManagement.Models;

using Tilework.Monitoring.Services;
using Tilework.Core.Persistence;


Expand Down Expand Up @@ -55,103 +54,103 @@ private ILoadBalancingConfigurator LoadConfigurator(IServiceProvider serviceProv
};
}

private static void ValidateRulePriority(ICollection<Rule> rules, int newPriority)
{
if (newPriority < 0)
{
throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, "Rule priority cannot be negative.");
private static void ValidateRulePriority(ICollection<Rule> rules, int newPriority)
{
if (newPriority < 0)
{
throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, "Rule priority cannot be negative.");
}

var maxPriority = rules.Count == 0 ? -1 : rules.Max(r => r.Priority);
if (newPriority > maxPriority + 1)
{
throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, $"Rule priority cannot be greater than {maxPriority + 1}.");
}
}

private static void EnsureRuleConditionsAllowed(LoadBalancer balancer, RuleDTO rule)
{
var invalid = rule.Conditions
.Select(condition => condition.Type)
.Distinct()
.Where(conditionType => !LoadBalancerConditionRules.IsAllowed(balancer.Protocol, conditionType))
.ToList();

if (invalid.Count == 0)
{
return;
}

var invalidList = string.Join(", ", invalid);
throw new ArgumentException(
$"Rule contains condition types not supported by {balancer.Protocol}: {invalidList}.",
nameof(rule));
}

private static void EnsureRuleActionAllowed(LoadBalancer balancer, RuleDTO rule)
{
if (rule.Action == null)
{
rule.Action = new RuleAction();
}

if (!LoadBalancerActionRules.IsAllowed(balancer.Type, rule.Action.Type))
{
throw new ArgumentException(
$"Rule action {rule.Action.Type} is not valid for load balancer type {balancer.Type}",
nameof(rule));
}

switch (rule.Action.Type)
{
case RuleActionType.Forward:
if (rule.TargetGroup == null || rule.TargetGroup == Guid.Empty)
{
throw new ArgumentException("Forward actions require a target group.", nameof(rule));
}
break;
case RuleActionType.Redirect:
rule.TargetGroup = null;
if (string.IsNullOrWhiteSpace(rule.Action.RedirectUrl))
{
throw new ArgumentException("Redirect actions require a destination URL.", nameof(rule));
}
if (rule.Action.RedirectStatusCode == null)
{
rule.Action.RedirectStatusCode = 302;
}
else if (rule.Action.RedirectStatusCode < 300 || rule.Action.RedirectStatusCode > 399)
{
throw new ArgumentException("Redirect status code must be in the 300-399 range.", nameof(rule));
}
break;
case RuleActionType.FixedResponse:
rule.TargetGroup = null;
if (rule.Action.FixedResponseStatusCode == null)
{
rule.Action.FixedResponseStatusCode = 200;
}
else if (rule.Action.FixedResponseStatusCode < 100 || rule.Action.FixedResponseStatusCode > 599)
{
throw new ArgumentException("Fixed response status code must be in the 100-599 range.", nameof(rule));
}
if (string.IsNullOrWhiteSpace(rule.Action.FixedResponseContentType))
{
rule.Action.FixedResponseContentType = "text/plain";
}
break;
case RuleActionType.Reject:
rule.TargetGroup = null;
break;
default:
throw new ArgumentOutOfRangeException(nameof(rule.Action.Type), rule.Action.Type, "Unknown rule action type.");
}
}

private static bool RequiresCertificate(LoadBalancer balancer)
{
return balancer.Protocol == LoadBalancerProtocol.HTTPS || balancer.Protocol == LoadBalancerProtocol.TLS;
}
throw new ArgumentOutOfRangeException(nameof(newPriority), newPriority, $"Rule priority cannot be greater than {maxPriority + 1}.");
}
}
private static void EnsureRuleConditionsAllowed(LoadBalancer balancer, RuleDTO rule)
{
var invalid = rule.Conditions
.Select(condition => condition.Type)
.Distinct()
.Where(conditionType => !LoadBalancerConditionRules.IsAllowed(balancer.Protocol, conditionType))
.ToList();
if (invalid.Count == 0)
{
return;
}
var invalidList = string.Join(", ", invalid);
throw new ArgumentException(
$"Rule contains condition types not supported by {balancer.Protocol}: {invalidList}.",
nameof(rule));
}
private static void EnsureRuleActionAllowed(LoadBalancer balancer, RuleDTO rule)
{
if (rule.Action == null)
{
rule.Action = new RuleAction();
}
if (!LoadBalancerActionRules.IsAllowed(balancer.Type, rule.Action.Type))
{
throw new ArgumentException(
$"Rule action {rule.Action.Type} is not valid for load balancer type {balancer.Type}",
nameof(rule));
}
switch (rule.Action.Type)
{
case RuleActionType.Forward:
if (rule.TargetGroup == null || rule.TargetGroup == Guid.Empty)
{
throw new ArgumentException("Forward actions require a target group.", nameof(rule));
}
break;
case RuleActionType.Redirect:
rule.TargetGroup = null;
if (string.IsNullOrWhiteSpace(rule.Action.RedirectUrl))
{
throw new ArgumentException("Redirect actions require a destination URL.", nameof(rule));
}
if (rule.Action.RedirectStatusCode == null)
{
rule.Action.RedirectStatusCode = 302;
}
else if (rule.Action.RedirectStatusCode < 300 || rule.Action.RedirectStatusCode > 399)
{
throw new ArgumentException("Redirect status code must be in the 300-399 range.", nameof(rule));
}
break;
case RuleActionType.FixedResponse:
rule.TargetGroup = null;
if (rule.Action.FixedResponseStatusCode == null)
{
rule.Action.FixedResponseStatusCode = 200;
}
else if (rule.Action.FixedResponseStatusCode < 100 || rule.Action.FixedResponseStatusCode > 599)
{
throw new ArgumentException("Fixed response status code must be in the 100-599 range.", nameof(rule));
}
if (string.IsNullOrWhiteSpace(rule.Action.FixedResponseContentType))
{
rule.Action.FixedResponseContentType = "text/plain";
}
break;
case RuleActionType.Reject:
rule.TargetGroup = null;
break;
default:
throw new ArgumentOutOfRangeException(nameof(rule.Action.Type), rule.Action.Type, "Unknown rule action type.");
}
}
private static bool RequiresCertificate(LoadBalancer balancer)
{
return balancer.Protocol == LoadBalancerProtocol.HTTPS || balancer.Protocol == LoadBalancerProtocol.TLS;
}

private static void EnsureCertificatesPresentIfRequired(LoadBalancer balancer)
{
Expand All @@ -174,21 +173,21 @@ public async Task<List<LoadBalancerDTO>> GetLoadBalancers()
return _mapper.Map<LoadBalancerDTO>(entity);
}

public async Task<LoadBalancerDTO> AddLoadBalancer(LoadBalancerDTO balancer)
{
EnsureProtocolAllowed(balancer);
var entity = _mapper.Map<LoadBalancer>(balancer);
public async Task<LoadBalancerDTO> AddLoadBalancer(LoadBalancerDTO balancer)
{
EnsureProtocolAllowed(balancer);
var entity = _mapper.Map<LoadBalancer>(balancer);

await _dbContext.LoadBalancers.AddAsync(entity);
await _dbContext.SaveChangesAsync();
return _mapper.Map<LoadBalancerDTO>(entity);
}

public async Task<LoadBalancerDTO> UpdateLoadBalancer(LoadBalancerDTO balancer)
{
EnsureProtocolAllowed(balancer);
var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id);
entity = _mapper.Map(balancer, entity);
public async Task<LoadBalancerDTO> UpdateLoadBalancer(LoadBalancerDTO balancer)
{
EnsureProtocolAllowed(balancer);
var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id);
entity = _mapper.Map(balancer, entity);

_dbContext.LoadBalancers.Update(entity);
await _dbContext.SaveChangesAsync();
Expand Down Expand Up @@ -263,12 +262,12 @@ public async Task<List<RuleDTO>> GetRules(LoadBalancerDTO balancer)
public async Task AddRule(LoadBalancerDTO balancer, RuleDTO rule)
{
var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id);
if (entity == null)
throw new ArgumentNullException(nameof(balancer));

ValidateRulePriority(entity.Rules, rule.Priority);
EnsureRuleConditionsAllowed(entity, rule);
EnsureRuleActionAllowed(entity, rule);
if (entity == null)
throw new ArgumentNullException(nameof(balancer));
ValidateRulePriority(entity.Rules, rule.Priority);
EnsureRuleConditionsAllowed(entity, rule);
EnsureRuleActionAllowed(entity, rule);

foreach (var existingRule in entity.Rules.Where(r => r.Priority >= rule.Priority))
{
Expand All @@ -287,12 +286,12 @@ public async Task UpdateRule(LoadBalancerDTO balancer, RuleDTO rule)
throw new ArgumentNullException(nameof(balancer));

var ruleEntity = entity.Rules.FirstOrDefault(t => t.Id == rule.Id);
if (ruleEntity == null)
return;

ValidateRulePriority(entity.Rules, rule.Priority);
EnsureRuleConditionsAllowed(entity, rule);
EnsureRuleActionAllowed(entity, rule);
if (ruleEntity == null)
return;
ValidateRulePriority(entity.Rules, rule.Priority);
EnsureRuleConditionsAllowed(entity, rule);
EnsureRuleActionAllowed(entity, rule);

await using var tx = await _dbContext.Database.BeginTransactionAsync();

Expand Down Expand Up @@ -393,24 +392,24 @@ public async Task AddCertificate(LoadBalancerDTO balancer, Guid certificateId)
await _dbContext.SaveChangesAsync();
}

public async Task RemoveCertificate(LoadBalancerDTO balancer, Guid certificateId)
public async Task RemoveCertificate(LoadBalancerDTO balancer, Guid certificateId)
{
var entity = await _dbContext.LoadBalancers.FindAsync(balancer.Id);
var c = entity.Certificates.FirstOrDefault(t => t.Id == certificateId);
entity.Certificates.Remove(c);
_dbContext.LoadBalancers.Update(entity);
await _dbContext.SaveChangesAsync();
}

private static void EnsureProtocolAllowed(LoadBalancerDTO balancer)
{
if (!LoadBalancerProtocolRules.IsAllowed(balancer.Type, balancer.Protocol))
{
throw new ArgumentException(
$"Protocol {balancer.Protocol} is not valid for load balancer type {balancer.Type}",
nameof(balancer));
}
}
await _dbContext.SaveChangesAsync();
}
private static void EnsureProtocolAllowed(LoadBalancerDTO balancer)
{
if (!LoadBalancerProtocolRules.IsAllowed(balancer.Type, balancer.Protocol))
{
throw new ArgumentException(
$"Protocol {balancer.Protocol} is not valid for load balancer type {balancer.Type}",
nameof(balancer));
}
}


public async Task<List<TargetGroupDTO>> GetTargetGroups()
Expand Down
3 changes: 3 additions & 0 deletions tilework.core/Services/Monitoring/MonitoringService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using Microsoft.Extensions.Logging;

using Tilework.Monitoring.Interfaces;
using Tilework.Monitoring.Models;

namespace Tilework.Monitoring.Services;

public class MonitoringService
{
Expand Down
Loading