Merge branch 'Cleanup' into 'main'

Program.cs cleanup, HiveModel change, log level change

See merge request kzotkin/hiveemulator!3
This commit is contained in:
Kirill 2025-02-20 14:41:51 +00:00
commit 08f30fc7fc
24 changed files with 294 additions and 186 deletions

View File

@ -37,7 +37,7 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
bool isSuccessfullyDisconnected = false; bool isSuccessfullyDisconnected = false;
try try
{ {
var result = await _redisService.DeleteAsync(hiveId); var result = await _redisService.DeleteAsync(GetHiveKey(hiveId));
isSuccessfullyDisconnected = result; isSuccessfullyDisconnected = result;
return result; return result;
} }
@ -65,9 +65,11 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
public async Task<HiveOperationalArea> ConnectHive(HiveModel model) public async Task<HiveOperationalArea> ConnectHive(HiveModel model)
{ {
_logger.LogInformation("Trying to connect Hive: {@model}", model);
bool result = await _redisService.SetAsync(GetHiveKey(model.HiveID), model); bool result = await _redisService.SetAsync(GetHiveKey(model.HiveID), model);
if (result) if (result)
{ {
_logger.LogInformation("Successfully connected Hive: {@model}", model);
var operationalArea = _spatialService.GetHiveOperationalArea(model); var operationalArea = _spatialService.GetHiveOperationalArea(model);
await _messageBus.Publish(new HiveConnectedMessage await _messageBus.Publish(new HiveConnectedMessage
{ {
@ -80,27 +82,34 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
} }
else else
{ {
await _messageBus.Publish(new HiveConnectedMessage _logger.LogError("Failed to connect Hive: {@model}", model);
{
HiveID = model.HiveID,
Hive = model,
IsSuccessfullyConnected = result
});
throw new HiveConnectionException($"Failed to connect hive for HiveId: {model.HiveID}"); throw new HiveConnectionException($"Failed to connect hive for HiveId: {model.HiveID}");
} }
} }
public async Task<bool> IsHiveConnected(string hiveId)
{
string hiveKey = GetHiveKey(hiveId);
return await _redisService.CheckIfKeyExists(hiveKey);
}
public async Task<DateTime> AddTelemetry(HiveTelemetryModel model) public async Task<DateTime> AddTelemetry(HiveTelemetryModel model)
{ {
string hiveKey = GetHiveKey(model.HiveID); string hiveKey = GetHiveKey(model.HiveID);
bool hiveExists = await _redisService.CheckIfKeyExists(hiveKey);
if (hiveExists)
{
bool result = await _redisService.UpdateAsync(hiveKey, (HiveModel hive) => bool result = await _redisService.UpdateAsync(hiveKey, (HiveModel hive) =>
{ {
hive.Telemetry = model; hive.Telemetry = model;
}); });
if (result)
{
_logger.LogInformation("Telemetry updated for HiveID: {hiveId}. Updated telemetry timestamp: {timestamp}", model.HiveID, model.Timestamp);
}
else
{
_logger.LogError("Failed to update Telemetry - Redis update issue. HiveID: {hiveId}, Telemetry model: {@telemetry}", model.HiveID, model);
}
await _messageBus.Publish(new TelemetrySentMessage await _messageBus.Publish(new TelemetrySentMessage
{ {
HiveID = model.HiveID, HiveID = model.HiveID,
@ -109,44 +118,33 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
}); });
return model.Timestamp; return model.Timestamp;
} }
else
{
await _messageBus.Publish(new TelemetrySentMessage
{
HiveID = model.HiveID,
Telemetry = model,
IsSuccessfullySent = false
});
throw new HiveNotFoundException($"Hive not found for id: {model.HiveID}");
}
}
public async Task<string> SendHiveControlSignal(string hiveId, Location destination) public async Task<string> SendHiveControlSignal(string hiveId, Location destination)
{ {
var hive = await GetHiveModel(hiveId); var hive = await GetHiveModel(hiveId);
if (hive == null) if (hive == null)
{ {
throw new Exception($"Hive control signal error: cannot find hive with id: {hiveId}"); _logger.LogError("Sending Hive Control signal: Hive not found for HiveID: {hiveId}", hiveId);
return null;
} }
bool isSuccessfullySent = false; bool isSuccessfullySent = false;
string hiveMindPath = _communicationControlConfiguration.CurrentValue.HiveMindPath;
try
{
var command = new MoveHiveMindCommand var command = new MoveHiveMindCommand
{ {
CommandType = State.Move, CommandType = State.Move,
Location = destination, Location = destination,
Timestamp = DateTime.Now Timestamp = DateTime.Now
}; };
try
var result = await _hiveHttpClient.SendHiveControlCommandAsync(_communicationControlConfiguration.CurrentValue.RequestScheme, {
hive.HiveIP, hive.HivePort, _communicationControlConfiguration.CurrentValue.HiveMindPath, command); var result = await _hiveHttpClient.SendHiveControlCommandAsync(hive.HiveSchema, hive.HiveIP, hive.HivePort, hiveMindPath, command);
isSuccessfullySent = true; isSuccessfullySent = true;
return result; return result;
} }
finally finally
{
if (isSuccessfullySent)
{ {
await _messageBus.Publish(new MoveHiveMessage await _messageBus.Publish(new MoveHiveMessage
{ {
@ -155,6 +153,12 @@ namespace DevOpsProject.CommunicationControl.Logic.Services
HiveID = hiveId HiveID = hiveId
}); });
} }
else
{
_logger.LogError("Failed to send control command for Hive: {@hive}, path: {path}, \n Command: {@command}", hive, hiveMindPath, command);
}
}
} }
private string GetHiveKey(string hiveId) private string GetHiveKey(string hiveId)

View File

@ -8,6 +8,7 @@ namespace DevOpsProject.CommunicationControl.Logic.Services.Interfaces
Task<HiveModel> GetHiveModel(string hiveId); Task<HiveModel> GetHiveModel(string hiveId);
Task<List<HiveModel>> GetAllHives(); Task<List<HiveModel>> GetAllHives();
Task<HiveOperationalArea> ConnectHive(HiveModel model); Task<HiveOperationalArea> ConnectHive(HiveModel model);
Task<bool> IsHiveConnected(string hiveId);
Task<DateTime> AddTelemetry(HiveTelemetryModel model); Task<DateTime> AddTelemetry(HiveTelemetryModel model);
Task<string> SendHiveControlSignal(string hiveId, Location destination); Task<string> SendHiveControlSignal(string hiveId, Location destination);
} }

View File

@ -0,0 +1,29 @@
using DevOpsProject.HiveMind.Logic.Services.Interfaces;
using DevOpsProject.HiveMind.Logic.Services;
using Asp.Versioning;
namespace DevOpsProject.HiveMind.API.DI
{
public static class ApiVersioningConfiguration
{
public static IServiceCollection AddApiVersioningConfiguration(this IServiceCollection serviceCollection)
{
serviceCollection.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-Api-Version")
);
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
return serviceCollection;
}
}
}

View File

@ -0,0 +1,21 @@
namespace DevOpsProject.HiveMind.API.DI
{
public static class CorsConfiguration
{
public static IServiceCollection AddCorsConfiguration(this IServiceCollection serviceCollection, string corsPolicyName)
{
serviceCollection.AddCors(options =>
{
options.AddPolicy(name: corsPolicyName,
policy =>
{
policy.AllowAnyOrigin() //SECURITY WARNING ! Never allow all origins
.AllowAnyMethod()
.AllowAnyHeader();
});
});
return serviceCollection;
}
}
}

View File

@ -0,0 +1,21 @@
using DevOpsProject.Shared.Clients;
using Polly.Extensions.Http;
using Polly;
namespace DevOpsProject.HiveMind.API.DI
{
public static class HttpClientsConfiguration
{
public static IServiceCollection AddHttpClientsConfiguration(this IServiceCollection serviceCollection)
{
var communicationControlTelemetryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
serviceCollection.AddHttpClient<HiveMindHttpClient>()
.AddPolicyHandler(communicationControlTelemetryPolicy);
serviceCollection.AddHttpClient("HiveConnectClient");
return serviceCollection;
}
}
}

View File

@ -0,0 +1,14 @@
using DevOpsProject.Shared.Configuration;
namespace DevOpsProject.HiveMind.API.DI
{
public static class OptionsConfiguration
{
public static IServiceCollection AddOptionsConfiguration(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.Configure<HiveCommunicationConfig>(configuration.GetSection("CommunicationConfiguration"));
return serviceCollection;
}
}
}

View File

@ -3,13 +3,10 @@ using Asp.Versioning.Builder;
using DevOpsProject.HiveMind.API.DI; using DevOpsProject.HiveMind.API.DI;
using DevOpsProject.HiveMind.API.Middleware; using DevOpsProject.HiveMind.API.Middleware;
using DevOpsProject.HiveMind.Logic.Services.Interfaces; using DevOpsProject.HiveMind.Logic.Services.Interfaces;
using DevOpsProject.Shared.Clients;
using DevOpsProject.Shared.Configuration; using DevOpsProject.Shared.Configuration;
using DevOpsProject.Shared.Models; using DevOpsProject.Shared.Models;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Polly;
using Polly.Extensions.Http;
using Serilog; using Serilog;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -19,52 +16,23 @@ builder.Host.UseSerilog((context, services, loggerConfig) =>
.ReadFrom.Services(services) .ReadFrom.Services(services)
.Enrich.FromLogContext()); .Enrich.FromLogContext());
builder.Services.AddApiVersioning(options => builder.Services.AddApiVersioningConfiguration();
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-Api-Version")
);
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddAuthorization(); builder.Services.AddAuthorization();
builder.Services.AddSwaggerGen(c => builder.Services.AddSwaggerGen(c =>
{ {
c.SwaggerDoc("v1", new OpenApiInfo { Title = "HiveMind - V1", Version = "v1.0" }); c.SwaggerDoc("v1", new OpenApiInfo { Title = "HiveMind - V1", Version = "v1.0" });
}); });
builder.Services.AddOptionsConfiguration(builder.Configuration);
builder.Services.AddHiveMindLogic(); builder.Services.AddHiveMindLogic();
builder.Services.Configure<HiveCommunicationConfig>(builder.Configuration.GetSection("CommunicationConfiguration")); builder.Services.AddHttpClientsConfiguration();
var communicationControlTelemetryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
builder.Services.AddHttpClient<HiveMindHttpClient>()
.AddPolicyHandler(communicationControlTelemetryPolicy);
// register NAMED client for connect request
builder.Services.AddHttpClient("HiveConnectClient");
string corsPolicyName = "HiveMindCorsPolicy"; string corsPolicyName = "HiveMindCorsPolicy";
builder.Services.AddCors(options => builder.Services.AddCorsConfiguration(corsPolicyName);
{
options.AddPolicy(name: corsPolicyName,
policy =>
{
policy.AllowAnyOrigin() //SECURITY WARNING ! Never allow all origins
.AllowAnyMethod()
.AllowAnyHeader();
});
});
builder.Services.AddExceptionHandler<ExceptionHandlingMiddleware>(); builder.Services.AddExceptionHandler<ExceptionHandlingMiddleware>();
builder.Services.AddProblemDetails(); builder.Services.AddProblemDetails();
@ -82,7 +50,7 @@ using (var scope = app.Services.CreateScope())
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError($"Error occured while connecting Hive to Communication Control. \nException text: {ex.Message}"); logger.LogError($"Error occured while connecting Hive to Communication Control. \nException text: {ex.Message}");
System.Diagnostics.Process.GetCurrentProcess().Kill(); Environment.Exit(1);
} }
} }
@ -105,7 +73,6 @@ ApiVersionSet apiVersionSet = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(1)) .HasApiVersion(new ApiVersion(1))
.ReportApiVersions() .ReportApiVersions()
.Build(); .Build();
RouteGroupBuilder groupBuilder = app.MapGroup("api/v{apiVersion:apiVersion}").WithApiVersionSet(apiVersionSet); RouteGroupBuilder groupBuilder = app.MapGroup("api/v{apiVersion:apiVersion}").WithApiVersionSet(apiVersionSet);
groupBuilder.MapGet("ping", (IOptionsSnapshot<HiveCommunicationConfig> config) => groupBuilder.MapGet("ping", (IOptionsSnapshot<HiveCommunicationConfig> config) =>

View File

@ -38,7 +38,7 @@
"rollingInterval": "Day", "rollingInterval": "Day",
"rollOnFileSizeLimit": true, "rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact", "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
"restrictedToMinimumLevel": "Warning" "restrictedToMinimumLevel": "Information"
} }
} }
], ],

View File

@ -27,6 +27,7 @@ namespace DevOpsProject.HiveMind.Logic.Services
// If already moving - stop movement // If already moving - stop movement
if (HiveInMemoryState.IsMoving) if (HiveInMemoryState.IsMoving)
{ {
_logger.LogWarning("Previous movement command terminated. Previous destination: {@destination}, Current Location: {@current}, new destination: {@destination}", HiveInMemoryState.Destination, HiveInMemoryState.CurrentLocation, destination);
StopMovement(); StopMovement();
} }
@ -39,8 +40,9 @@ namespace DevOpsProject.HiveMind.Logic.Services
if (_movementTimer == null) if (_movementTimer == null)
{ {
// TODO: Recalculating position each N seconds // TODO: Recalculating position each N seconds
_movementTimer = new Timer(UpdateMovement, null, TimeSpan.Zero, TimeSpan.FromSeconds(3)); int intervalFromSeconds = 3;
_logger.LogInformation("Movement timer started."); _movementTimer = new Timer(UpdateMovement, null, TimeSpan.Zero, TimeSpan.FromSeconds(intervalFromSeconds));
_logger.LogInformation("Movement timer started. Destination: {@destination}, recalculation interval: {interval}", destination, intervalFromSeconds);
} }
} }
} }
@ -60,14 +62,13 @@ namespace DevOpsProject.HiveMind.Logic.Services
if (AreLocationsEqual(currentLocation.Value, destination.Value)) if (AreLocationsEqual(currentLocation.Value, destination.Value))
{ {
_logger.LogInformation("Reached destination. Current location: {@currentLocation}, Destination: {@destination}", currentLocation, destination);
StopMovement(); StopMovement();
return; return;
} }
Location newLocation = CalculateNextPosition(currentLocation.Value, destination.Value, 0.1f); Location newLocation = CalculateNextPosition(currentLocation.Value, destination.Value, 0.1f);
HiveInMemoryState.CurrentLocation = newLocation; HiveInMemoryState.CurrentLocation = newLocation;
_logger.LogInformation($"Moved closer: {newLocation}");
} }
} }
@ -77,7 +78,6 @@ namespace DevOpsProject.HiveMind.Logic.Services
_movementTimer = null; _movementTimer = null;
HiveInMemoryState.IsMoving = false; HiveInMemoryState.IsMoving = false;
HiveInMemoryState.Destination = null; HiveInMemoryState.Destination = null;
_logger.LogInformation("Movement stopped: Reached destination.");
} }
private static bool AreLocationsEqual(Location loc1, Location loc2) private static bool AreLocationsEqual(Location loc1, Location loc2)

View File

@ -31,6 +31,7 @@ namespace DevOpsProject.HiveMind.Logic.Services
{ {
var request = new HiveConnectRequest var request = new HiveConnectRequest
{ {
HiveSchema = _communicationConfigurationOptions.RequestSchema,
HiveIP = _communicationConfigurationOptions.HiveIP, HiveIP = _communicationConfigurationOptions.HiveIP,
HivePort = _communicationConfigurationOptions.HivePort, HivePort = _communicationConfigurationOptions.HivePort,
HiveID = _communicationConfigurationOptions.HiveID HiveID = _communicationConfigurationOptions.HiveID
@ -47,6 +48,8 @@ namespace DevOpsProject.HiveMind.Logic.Services
}; };
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
_logger.LogInformation("Attempting to connect Hive. Request: {@request}, URI: {uri}", request, uriBuilder.Uri);
var retryPolicy = Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode) var retryPolicy = Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync( .WaitAndRetryAsync(
10, 10,
@ -68,7 +71,6 @@ namespace DevOpsProject.HiveMind.Logic.Services
HiveInMemoryState.OperationalArea = hiveConnectResponse.OperationalArea; HiveInMemoryState.OperationalArea = hiveConnectResponse.OperationalArea;
HiveInMemoryState.CurrentLocation = _communicationConfigurationOptions.InitialLocation; HiveInMemoryState.CurrentLocation = _communicationConfigurationOptions.InitialLocation;
// HERE - we are starting to send telemetry
StartTelemetry(); StartTelemetry();
} }
else else
@ -80,7 +82,7 @@ namespace DevOpsProject.HiveMind.Logic.Services
else else
{ {
_logger.LogError($"Failed to connect hive, terminating process"); _logger.LogError($"Failed to connect hive, terminating process");
System.Diagnostics.Process.GetCurrentProcess().Kill(); Environment.Exit(1);
} }
} }

View File

@ -2,7 +2,6 @@
{ {
public class ComControlCommunicationConfiguration public class ComControlCommunicationConfiguration
{ {
public string RequestScheme { get; set; }
public string HiveMindPath { get; set; } public string HiveMindPath { get; set; }
} }
} }

View File

@ -1,19 +0,0 @@
namespace DevOpsProject.Shared.Exceptions
{
public class HiveNotFoundException : Exception
{
public HiveNotFoundException()
{
}
public HiveNotFoundException(string message)
: base(message)
{
}
public HiveNotFoundException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@ -1,13 +1,8 @@
using System; namespace DevOpsProject.Shared.Models
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DevOpsProject.Shared.Models
{ {
public class HiveConnectRequest public class HiveConnectRequest
{ {
public string HiveSchema { get; set; }
public string HiveIP { get; set; } public string HiveIP { get; set; }
public int HivePort { get; set; } public int HivePort { get; set; }
public string HiveID { get; set; } public string HiveID { get; set; }

View File

@ -5,6 +5,7 @@
public string HiveID { get; set; } public string HiveID { get; set; }
public string HiveIP { get; set; } public string HiveIP { get; set; }
public int HivePort { get; set; } public int HivePort { get; set; }
public string HiveSchema { get; set; }
public HiveTelemetryModel Telemetry { get; set; } public HiveTelemetryModel Telemetry { get; set; }
} }
} }

View File

@ -32,17 +32,21 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
[HttpGet("hive/{hiveId}")] [HttpGet("hive/{hiveId}")]
public async Task<IActionResult> GetHive(string hiveId) public async Task<IActionResult> GetHive(string hiveId)
{ {
var hiveModel = await _communicationControlService.GetHiveModel(hiveId); var hiveExists = await _communicationControlService.IsHiveConnected(hiveId);
if (!hiveExists)
{
_logger.LogWarning("Failed to get Hive for HiveID: {hiveId}", hiveId);
return NotFound($"Hive with HiveID: {hiveId} is not found");
}
var hiveModel = await _communicationControlService.GetHiveModel(hiveId);
return Ok(hiveModel); return Ok(hiveModel);
} }
[HttpGet("hive")] [HttpGet("hive")]
public async Task<IActionResult> GetHives() public async Task<IActionResult> GetHives()
{ {
var hives = await _communicationControlService.GetAllHives(); var hives = await _communicationControlService.GetAllHives();
return Ok(hives); return Ok(hives);
} }
@ -59,6 +63,7 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
if (request?.Hives == null || !request.Hives.Any()) if (request?.Hives == null || !request.Hives.Any())
return BadRequest("No hive IDs provided."); return BadRequest("No hive IDs provided.");
_logger.LogInformation("Hive moving request accepted by enpdoint. Request: {@request}", request);
foreach (var id in request.Hives) foreach (var id in request.Hives)
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
@ -69,7 +74,7 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, $"Failed to send control signal for HiveID: {id}"); _logger.LogError(ex, "Failed to send control signal for HiveID: {id} \n Request: {@request}", id, request);
} }
}); });
} }

View File

@ -11,10 +11,12 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
public class HiveController : Controller public class HiveController : Controller
{ {
private readonly ICommunicationControlService _communicationControlService; private readonly ICommunicationControlService _communicationControlService;
private readonly ILogger<HiveController> _logger;
public HiveController(ICommunicationControlService communicationControlService) public HiveController(ICommunicationControlService communicationControlService, ILogger<HiveController> logger)
{ {
_communicationControlService = communicationControlService; _communicationControlService = communicationControlService;
_logger = logger;
} }
[HttpPost("connect")] [HttpPost("connect")]
@ -25,8 +27,16 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
HiveID = request.HiveID, HiveID = request.HiveID,
HiveIP = request.HiveIP, HiveIP = request.HiveIP,
HivePort = request.HivePort, HivePort = request.HivePort,
HiveSchema = request.HiveSchema
}; };
bool isConnected = await _communicationControlService.IsHiveConnected(request.HiveID);
if (isConnected)
{
_logger.LogError("Hive with HiveID: {hiveId} already connected. Request: {@request}", request.HiveID, request);
return BadRequest($"Hive with HiveID: {request.HiveID} already connected");
}
var hiveOperationalArea = await _communicationControlService.ConnectHive(hiveModel); var hiveOperationalArea = await _communicationControlService.ConnectHive(hiveModel);
var connectResponse = new HiveConnectResponse var connectResponse = new HiveConnectResponse
{ {
@ -35,6 +45,7 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
}; };
return Ok(connectResponse); return Ok(connectResponse);
} }
[HttpPost("telemetry")] [HttpPost("telemetry")]
@ -50,6 +61,9 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
Timestamp = DateTime.Now Timestamp = DateTime.Now
}; };
bool isHiveConnected = await _communicationControlService.IsHiveConnected(request.HiveID);
if (isHiveConnected)
{
var telemetryUpdateTimestamp = await _communicationControlService.AddTelemetry(hiveTelemetryModel); var telemetryUpdateTimestamp = await _communicationControlService.AddTelemetry(hiveTelemetryModel);
var telemetryResponse = new HiveTelemetryResponse var telemetryResponse = new HiveTelemetryResponse
{ {
@ -58,6 +72,12 @@ namespace DevOpsProject.CommunicationControl.API.Controllers
return Ok(telemetryResponse); return Ok(telemetryResponse);
} }
else
{
_logger.LogWarning("Failed to write telemetry. Hive with HiveID: {hiveId} is not connected. Request: {@request}", request.HiveID, request);
return NotFound($"Failed to write telemetry. Hive with HiveID: {request.HiveID} is not connected");
}
}
} }
} }

View File

@ -0,0 +1,27 @@
using Asp.Versioning;
namespace DevOpsProject.CommunicationControl.API.DI
{
public static class ApiVersioningConfiguration
{
public static IServiceCollection AddApiVersioningConfiguration(this IServiceCollection serviceCollection)
{
serviceCollection.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-Api-Version")
);
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
return serviceCollection;
}
}
}

View File

@ -0,0 +1,21 @@
namespace DevOpsProject.CommunicationControl.API.DI
{
public static class CorsConfiguration
{
public static IServiceCollection AddCorsConfiguration(this IServiceCollection serviceCollection, string corsPolicyName)
{
serviceCollection.AddCors(options =>
{
options.AddPolicy(name: corsPolicyName,
policy =>
{
policy.AllowAnyOrigin() //SECURITY WARNING ! Never allow all origins
.AllowAnyMethod()
.AllowAnyHeader();
});
});
return serviceCollection;
}
}
}

View File

@ -0,0 +1,20 @@
using DevOpsProject.Shared.Clients;
using Polly;
using Polly.Extensions.Http;
namespace DevOpsProject.CommunicationControl.API.DI
{
public static class HttpClientsConfiguration
{
public static IServiceCollection AddHttpClientsConfiguration(this IServiceCollection serviceCollection)
{
var hiveRetryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
serviceCollection.AddHttpClient<CommunicationControlHttpClient>()
.AddPolicyHandler(hiveRetryPolicy);
return serviceCollection;
}
}
}

View File

@ -0,0 +1,15 @@
namespace DevOpsProject.CommunicationControl.API.DI
{
public static class JsonControllerOptionsConfiguration
{
public static IServiceCollection AddJsonControllerOptionsConfiguration(this IServiceCollection serviceCollection)
{
serviceCollection.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
return serviceCollection;
}
}
}

View File

@ -12,7 +12,5 @@ namespace DevOpsProject.CommunicationControl.API.DI
return serviceCollection; return serviceCollection;
} }
} }
} }

View File

@ -0,0 +1,16 @@
using DevOpsProject.Shared.Configuration;
using DevOpsProject.Shared.Models;
namespace DevOpsProject.CommunicationControl.API.DI
{
public static class OptionsConfiguration
{
public static IServiceCollection AddOptionsConfiguration(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.Configure<OperationalAreaConfig>(configuration.GetSection("OperationalArea"));
serviceCollection.Configure<ComControlCommunicationConfiguration>(configuration.GetSection("CommunicationConfiguration"));
return serviceCollection;
}
}
}

View File

@ -1,13 +1,6 @@
using Asp.Versioning;
using DevOpsProject.CommunicationControl.API.DI; using DevOpsProject.CommunicationControl.API.DI;
using DevOpsProject.CommunicationControl.API.Middleware; using DevOpsProject.CommunicationControl.API.Middleware;
using DevOpsProject.Shared.Clients;
using DevOpsProject.Shared.Configuration;
using DevOpsProject.Shared.Models;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Polly;
using Polly.Extensions.Http;
using Serilog; using Serilog;
internal class Program internal class Program
@ -21,26 +14,11 @@ internal class Program
.ReadFrom.Services(services) .ReadFrom.Services(services)
.Enrich.FromLogContext()); .Enrich.FromLogContext());
builder.Services.AddApiVersioning(options => builder.Services.AddApiVersioningConfiguration();
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-Api-Version")
);
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
// TODO: consider this approach // TODO: consider this approach
builder.Services.AddControllers().AddJsonOptions(options => builder.Services.AddJsonControllerOptionsConfiguration();
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
}); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c => builder.Services.AddSwaggerGen(c =>
{ {
@ -52,36 +30,12 @@ internal class Program
builder.Services.AddRedis(builder.Configuration); builder.Services.AddRedis(builder.Configuration);
builder.Services.AddCommunicationControlLogic(); builder.Services.AddCommunicationControlLogic();
builder.Services.Configure<OperationalAreaConfig>(builder.Configuration.GetSection("OperationalArea")); builder.Services.AddOptionsConfiguration(builder.Configuration);
builder.Services.Configure<ComControlCommunicationConfiguration>(builder.Configuration.GetSection("CommunicationConfiguration"));
var hiveRetryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
builder.Services.AddHttpClient<CommunicationControlHttpClient>()
.AddPolicyHandler(hiveRetryPolicy);
builder.Services.AddHttpClientsConfiguration();
var corsPolicyName = "AllowReactApp"; var corsPolicyName = "AllowReactApp";
var localCorsPolicyName = "AllowLocalHtml"; builder.Services.AddCorsConfiguration(corsPolicyName);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: corsPolicyName,
policy =>
{
policy.AllowAnyOrigin() //SECURITY WARNING ! Never allow all origins
.AllowAnyMethod()
.AllowAnyHeader();
});
options.AddPolicy(name: localCorsPolicyName,
policy =>
{
policy.AllowAnyOrigin() //SECURITY WARNING ! Never allow all origins
.AllowAnyMethod()
.AllowAnyHeader();
});
});
builder.Services.AddExceptionHandler<ExceptionHandlingMiddleware>(); builder.Services.AddExceptionHandler<ExceptionHandlingMiddleware>();
builder.Services.AddProblemDetails(); builder.Services.AddProblemDetails();
@ -97,7 +51,6 @@ internal class Program
} }
app.UseCors(corsPolicyName); app.UseCors(corsPolicyName);
//app.UseCors(localCorsPolicyName);
app.UseAuthorization(); app.UseAuthorization();

View File

@ -14,10 +14,8 @@
"InitialSpeed_KM": 5, "InitialSpeed_KM": 5,
"TelemetryInterval_MS": 30000, "TelemetryInterval_MS": 30000,
"PingInterval_MS": 15000 "PingInterval_MS": 15000
}, },
"CommunicationConfiguration": { "CommunicationConfiguration": {
"RequestScheme": "http",
"HiveMindPath": "api/v1" "HiveMindPath": "api/v1"
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
@ -45,7 +43,7 @@
"rollingInterval": "Day", "rollingInterval": "Day",
"rollOnFileSizeLimit": true, "rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact", "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
"restrictedToMinimumLevel": "Warning" "restrictedToMinimumLevel": "Information"
} }
} }
], ],