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
Expand Up @@ -137,7 +137,7 @@ public async Task UpdateHost_ReplacesExistingHostWithSameHostname()
}

[Fact]
public async Task UpdateHost_WithNullHostIPs_SetsEmptyHostIPs()
public async Task UpdateHost_WithNullHostIPs_RemovesHostFromCache()
{
SetupAuthenticatedUser("cluster-1");
_cacheMock.Setup(x => x.GetHostsAsync("cluster-1"))
Expand All @@ -154,13 +154,12 @@ public async Task UpdateHost_WithNullHostIPs_SetsEmptyHostIPs()
_cacheMock.Verify(x => x.SetClusterCacheAsync(
"cluster-1",
It.Is<Models.Core.Host[]>(hosts =>
hosts.Length == 1 &&
hosts[0].HostIPs.Length == 0)),
hosts.Length == 0)),
Times.Once);
}

[Fact]
public async Task UpdateHost_WithEmptyHostIPs_SetsEmptyHostIPs()
public async Task UpdateHost_WithEmptyHostIPs_RemovesHostFromCache()
{
SetupAuthenticatedUser("cluster-1");
_cacheMock.Setup(x => x.GetHostsAsync("cluster-1"))
Expand All @@ -177,8 +176,7 @@ public async Task UpdateHost_WithEmptyHostIPs_SetsEmptyHostIPs()
_cacheMock.Verify(x => x.SetClusterCacheAsync(
"cluster-1",
It.Is<Models.Core.Host[]>(hosts =>
hosts.Length == 1 &&
hosts[0].HostIPs.Length == 0)),
hosts.Length == 0)),
Times.Once);
}

Expand Down
273 changes: 141 additions & 132 deletions src/Cyclops.MultiCluster/Controllers/HostController.cs
Original file line number Diff line number Diff line change
@@ -1,132 +1,141 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Cyclops.MultiCluster.Models.Api;
using Cyclops.MultiCluster.Services;

namespace Cyclops.MultiCluster.Controllers
{
/// <summary>
/// Host based operations happen here
/// </summary>
[ApiController]
[Authorize]
[Route("[controller]")]
public class HostController : ControllerBase
{
private readonly ILogger<HostController> _logger;
private readonly ICache _cache;
private readonly IOptions<MultiClusterOptions> _options;

/// <summary>
/// Host based operations constructor
/// </summary>
/// <param name="logger"></param>
/// <param name="cache"></param>
/// <param name="options"></param>
public HostController(ILogger<HostController> logger, ICache cache, IOptions<MultiClusterOptions> options)
{
_logger = logger;
_cache = cache;
_options = options;
}

/// <summary>
/// Updates the host with the new ip's
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
[ProducesResponseType(500)]
public async Task<ActionResult> UpdateHost([FromBody] HostModel model)
{
using var scope = _logger.BeginScope(new { hostname = model.Hostname });

_logger.LogInformation("Received host update");
_logger.LogDebug("Model {@model}", model);

try
{
if (!ModelState.IsValid)
{
_logger.LogWarning("Invalid request state {@model}", model);
return BadRequest(ModelState);
}

var clusterIdentifier = User.Identity!.Name!;
var hostIPs = Array.Empty<Models.Core.HostIP>();

if (model.HostIPs != null && model.HostIPs.Any())
{
hostIPs = model.HostIPs.Select(ip => new Models.Core.HostIP
{
IPAddress = ip.IPAddress,
Priority = ip.Priority,
Weight = ip.Weight,
ClusterIdentifier = clusterIdentifier
}).ToArray();
}

var cluster = await _cache.GetHostsAsync(clusterIdentifier);
var hosts = cluster?.Where(x => x.Hostname != model.Hostname).ToList() ?? new List<Models.Core.Host>();
hosts.Add(new Models.Core.Host
{
Hostname = model.Hostname,
HostIPs = hostIPs
});
await _cache.SetClusterCacheAsync(clusterIdentifier, hosts.ToArray());

return NoContent();
}
catch (Exception exception)
{
_logger.LogError(exception, "Error updating host with {@model}", model);
return base.Problem(exception.Message);
}
}

/// <summary>
/// Get all hosts in this cluster
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(HostModel[]), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<HostModel[]>> Get()
{
_logger.LogInformation("Getting a list of local hosts");
try
{
var clusterIdentifier = _options.Value.ClusterIdentifier;
var hosts = await _cache.GetHostsAsync(clusterIdentifier);

if (hosts == null)
{
_logger.LogWarning("Hosts for the local cluster is not found.");
return NotFound();
}

var result = hosts.Select(host => new HostModel
{
HostIPs = host.HostIPs.Select(ip => new HostIP
{
IPAddress = ip.IPAddress,
Priority = ip.Priority,
Weight = ip.Weight
}).ToArray(),
Hostname = host.Hostname
});

return Ok(result);
}
catch (Exception exception)
{
_logger.LogError(exception, "Unable to get local cluster hosts");
throw;
}
}
}
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Cyclops.MultiCluster.Models.Api;
using Cyclops.MultiCluster.Services;

namespace Cyclops.MultiCluster.Controllers
{
/// <summary>
/// Host based operations happen here
/// </summary>
[ApiController]
[Authorize]
[Route("[controller]")]
public class HostController : ControllerBase
{
private readonly ILogger<HostController> _logger;
private readonly ICache _cache;
private readonly IOptions<MultiClusterOptions> _options;

/// <summary>
/// Host based operations constructor
/// </summary>
/// <param name="logger"></param>
/// <param name="cache"></param>
/// <param name="options"></param>
public HostController(ILogger<HostController> logger, ICache cache, IOptions<MultiClusterOptions> options)
{
_logger = logger;
_cache = cache;
_options = options;
}

/// <summary>
/// Updates the host with the new ip's
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
[ProducesResponseType(500)]
public async Task<ActionResult> UpdateHost([FromBody] HostModel model)
{
using var scope = _logger.BeginScope(new { hostname = model.Hostname });

_logger.LogInformation("Received host update");
_logger.LogDebug("Model {@model}", model);

try
{
if (!ModelState.IsValid)
{
_logger.LogWarning("Invalid request state {@model}", model);
return BadRequest(ModelState);
}

var clusterIdentifier = User.Identity!.Name!;
var hostIPs = Array.Empty<Models.Core.HostIP>();

if (model.HostIPs != null && model.HostIPs.Any())
{
hostIPs = model.HostIPs.Select(ip => new Models.Core.HostIP
{
IPAddress = ip.IPAddress,
Priority = ip.Priority,
Weight = ip.Weight,
ClusterIdentifier = clusterIdentifier
}).ToArray();
}

var cluster = await _cache.GetHostsAsync(clusterIdentifier);
var hosts = cluster?.Where(x => x.Hostname != model.Hostname).ToList() ?? new List<Models.Core.Host>();

if (hostIPs.Length > 0)
{
hosts.Add(new Models.Core.Host
{
Hostname = model.Hostname,
HostIPs = hostIPs
});
}
else
{
_logger.LogInformation("Removing host {hostname} from cluster cache for {clusterIdentifier}", model.Hostname, clusterIdentifier);
}

await _cache.SetClusterCacheAsync(clusterIdentifier, hosts.ToArray());

return NoContent();
}
catch (Exception exception)
{
_logger.LogError(exception, "Error updating host with {@model}", model);
return base.Problem(exception.Message);
}
}

/// <summary>
/// Get all hosts in this cluster
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(HostModel[]), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<HostModel[]>> Get()
{
_logger.LogInformation("Getting a list of local hosts");
try
{
var clusterIdentifier = _options.Value.ClusterIdentifier;
var hosts = await _cache.GetHostsAsync(clusterIdentifier);

if (hosts == null)
{
_logger.LogWarning("Hosts for the local cluster is not found.");
return NotFound();
}

var result = hosts.Select(host => new HostModel
{
HostIPs = host.HostIPs.Select(ip => new HostIP
{
IPAddress = ip.IPAddress,
Priority = ip.Priority,
Weight = ip.Weight
}).ToArray(),
Hostname = host.Hostname
});

return Ok(result);
}
catch (Exception exception)
{
_logger.LogError(exception, "Unable to get local cluster hosts");
throw;
}
}
}
}
Loading
Loading