diff --git a/src/CommunicationControl/DevOpsProject.HiveMind.API/Controllers/HiveMindController.cs b/src/CommunicationControl/DevOpsProject.HiveMind.API/Controllers/HiveMindController.cs deleted file mode 100644 index 9f50d98..0000000 --- a/src/CommunicationControl/DevOpsProject.HiveMind.API/Controllers/HiveMindController.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Asp.Versioning; -using DevOpsProject.HiveMind.Logic.Services.Interfaces; -using DevOpsProject.Shared.Configuration; -using DevOpsProject.Shared.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; - -namespace DevOpsProject.HiveMind.API.Controllers -{ - [ApiVersion("1.0")] - [ApiController] - [Route("api/v{version:apiVersion}")] - public class HiveMindController : Controller - { - private readonly IHiveMindService _hiveMindService; - private readonly IHiveMindMovingService _hiveMindMovingService; - - public HiveMindController(IHiveMindService hiveMindService, IHiveMindMovingService hiveMindMovingService) - { - _hiveMindService = hiveMindService; - _hiveMindMovingService = hiveMindMovingService; - } - - [HttpGet("ping")] - public IActionResult Ping(IOptionsSnapshot config) - { - return Ok(new - { - Timestamp = DateTime.Now, - ID = config.Value.HiveID - }); - } - - [HttpPost("connect")] - public async Task TriggerConnectHive() - { - await _hiveMindService.ConnectHive(); - return Ok(); - } - - [HttpPost("command")] - public IActionResult MoveHideMind(MoveHiveMindCommand command) - { - _hiveMindMovingService.MoveToLocation(command.Location); - return Ok(); - } - - } -} diff --git a/src/CommunicationControl/DevOpsProject.HiveMind.API/Program.cs b/src/CommunicationControl/DevOpsProject.HiveMind.API/Program.cs index a5dde80..65003c5 100644 --- a/src/CommunicationControl/DevOpsProject.HiveMind.API/Program.cs +++ b/src/CommunicationControl/DevOpsProject.HiveMind.API/Program.cs @@ -1,94 +1,126 @@ using Asp.Versioning; +using Asp.Versioning.Builder; using DevOpsProject.HiveMind.API.DI; using DevOpsProject.HiveMind.API.Middleware; +using DevOpsProject.HiveMind.Logic.Services.Interfaces; using DevOpsProject.Shared.Clients; using DevOpsProject.Shared.Configuration; +using DevOpsProject.Shared.Models; +using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Polly; using Polly.Extensions.Http; using Serilog; -internal class Program +var builder = WebApplication.CreateBuilder(args); + +builder.Host.UseSerilog((context, services, loggerConfig) => + loggerConfig.ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext()); + +builder.Services.AddApiVersioning(options => { - private static void Main(string[] args) + 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.AddAuthorization(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "HiveMind - V1", Version = "v1.0" }); +}); +builder.Services.AddHiveMindLogic(); + +builder.Services.Configure(builder.Configuration.GetSection("CommunicationConfiguration")); + +var communicationControlTelemetryPolicy = HttpPolicyExtensions + .HandleTransientHttpError() + .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); +builder.Services.AddHttpClient() + .AddPolicyHandler(communicationControlTelemetryPolicy); + +// register NAMED client for connect request +builder.Services.AddHttpClient("HiveConnectClient"); + +string corsPolicyName = "HiveMindCorsPolicy"; +builder.Services.AddCors(options => +{ + options.AddPolicy(name: corsPolicyName, + policy => + { + policy.AllowAnyOrigin() //SECURITY WARNING ! Never allow all origins + .AllowAnyMethod() + .AllowAnyHeader(); + }); +}); + +builder.Services.AddExceptionHandler(); +builder.Services.AddProblemDetails(); + +var app = builder.Build(); + +using (var scope = app.Services.CreateScope()) +{ + var logger = scope.ServiceProvider.GetRequiredService>(); + try { - var builder = WebApplication.CreateBuilder(args); - - builder.Host.UseSerilog((context, services, loggerConfig) => - loggerConfig.ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext()); - - builder.Services.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; - }); - - // TODO: double check following approach - builder.Services.AddControllers().AddJsonOptions(options => - { - options.JsonSerializerOptions.PropertyNamingPolicy = null; - }); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "HiveMind - V1", Version = "v1.0" }); - }); - builder.Services.AddHiveMindLogic(); - - builder.Services.Configure(builder.Configuration.GetSection("CommunicationConfiguration")); - - var communicationControlRetryPolicy = HttpPolicyExtensions - .HandleTransientHttpError() - .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); - builder.Services.AddHttpClient() - .AddPolicyHandler(communicationControlRetryPolicy); - - string corsPolicyName = "HiveMindCorsPolicy"; - builder.Services.AddCors(options => - { - options.AddPolicy(name: corsPolicyName, - policy => - { - policy.AllowAnyOrigin() //SECURITY WARNING ! Never allow all origins - .AllowAnyMethod() - .AllowAnyHeader(); - }); - }); - - builder.Services.AddExceptionHandler(); - builder.Services.AddProblemDetails(); - - var app = builder.Build(); - - app.UseExceptionHandler(); - - // Configure the HTTP request pipeline. - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - } - - app.UseCors(corsPolicyName); - - //app.UseHttpsRedirection(); - - app.UseAuthorization(); - - app.MapControllers(); - - app.Run(); + var hiveMindService = scope.ServiceProvider.GetRequiredService(); + await hiveMindService.ConnectHive(); } -} \ No newline at end of file + catch (Exception ex) + { + logger.LogError($"Error occured while connecting Hive to Communication Control. \nException text: {ex.Message}"); + System.Diagnostics.Process.GetCurrentProcess().Kill(); + } +} + +app.UseExceptionHandler(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseCors(corsPolicyName); + +//app.UseHttpsRedirection(); + +app.UseAuthorization(); + +ApiVersionSet apiVersionSet = app.NewApiVersionSet() + .HasApiVersion(new ApiVersion(1)) + .ReportApiVersions() + .Build(); + +RouteGroupBuilder groupBuilder = app.MapGroup("api/v{apiVersion:apiVersion}").WithApiVersionSet(apiVersionSet); + +groupBuilder.MapGet("ping", (IOptionsSnapshot config) => +{ + return Results.Ok(new + { + Timestamp = DateTime.Now, + ID = config.Value.HiveID + }); +}); + +groupBuilder.MapPost("command", (MoveHiveMindCommand command, IHiveMindMovingService hiveMindMovingService) => +{ + hiveMindMovingService.MoveToLocation(command.Location); + return Results.Ok(); +}); + +app.Run(); diff --git a/src/CommunicationControl/DevOpsProject.HiveMind.API/appsettings.json b/src/CommunicationControl/DevOpsProject.HiveMind.API/appsettings.json index e2b3c90..f12b9a4 100644 --- a/src/CommunicationControl/DevOpsProject.HiveMind.API/appsettings.json +++ b/src/CommunicationControl/DevOpsProject.HiveMind.API/appsettings.json @@ -1,4 +1,20 @@ { + + "CommunicationConfiguration": { + "RequestSchema": "http", + "CommunicationControlIP": "localhost", + "CommunicationControlPort": 8080, + "CommunicationControlPath": "api/v1/hive", + "HiveIP": "localhost", + "HivePort": 5149, + "HiveID": "1", + "InitialLocation": { + "Latitude": 48.719547, + "Longitude": 38.092680 + } + }, + "AllowedHosts": "*", + "Urls": "http://0.0.0.0:5149", "Serilog": { "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "MinimumLevel": { @@ -31,19 +47,6 @@ "Application": "DevOpsProject.HiveMind", "Environment": "Development" } - }, - "CommunicationConfiguration": { - "RequestSchema": "http", - "CommunicationControlIP": "localhost", - "CommunicationControlPort": 8080, - "CommunicationControlPath": "api/v1/hive", - "HiveIP": "localhost", - "HivePort": 5149, - "HiveID": "1", - "InitialLocation": { - "Latitude": 48.719547, - "Longitude": 38.092680 - } - }, - "AllowedHosts": "*" + } + } diff --git a/src/CommunicationControl/DevOpsProject.HiveMind.Logic/DevOpsProject.HiveMind.Logic.csproj b/src/CommunicationControl/DevOpsProject.HiveMind.Logic/DevOpsProject.HiveMind.Logic.csproj index b8963cf..d4a4a04 100644 --- a/src/CommunicationControl/DevOpsProject.HiveMind.Logic/DevOpsProject.HiveMind.Logic.csproj +++ b/src/CommunicationControl/DevOpsProject.HiveMind.Logic/DevOpsProject.HiveMind.Logic.csproj @@ -7,6 +7,13 @@ + + + + + + + @@ -15,8 +22,4 @@ - - - - diff --git a/src/CommunicationControl/DevOpsProject.HiveMind.Logic/Services/HiveMindService.cs b/src/CommunicationControl/DevOpsProject.HiveMind.Logic/Services/HiveMindService.cs index 8675912..4d3204f 100644 --- a/src/CommunicationControl/DevOpsProject.HiveMind.Logic/Services/HiveMindService.cs +++ b/src/CommunicationControl/DevOpsProject.HiveMind.Logic/Services/HiveMindService.cs @@ -6,19 +6,23 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Text.Json; using DevOpsProject.HiveMind.Logic.State; +using System.Text; +using Polly; namespace DevOpsProject.HiveMind.Logic.Services { public class HiveMindService : IHiveMindService { + private readonly IHttpClientFactory _httpClientFactory; private readonly HiveMindHttpClient _httpClient; private readonly ILogger _logger; private readonly HiveCommunicationConfig _communicationConfigurationOptions; private Timer _telemetryTimer; - public HiveMindService(HiveMindHttpClient httpClient, ILogger logger, IOptionsSnapshot communicationConfigurationOptions) + public HiveMindService(IHttpClientFactory httpClientFactory, HiveMindHttpClient httpClient, ILogger logger, IOptionsSnapshot communicationConfigurationOptions) { _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _logger = logger; _communicationConfigurationOptions = communicationConfigurationOptions.Value; } @@ -31,16 +35,33 @@ namespace DevOpsProject.HiveMind.Logic.Services HivePort = _communicationConfigurationOptions.HivePort, HiveID = _communicationConfigurationOptions.HiveID }; - - var connectResult = await _httpClient.SendCommunicationControlConnectAsync(_communicationConfigurationOptions.RequestSchema, - _communicationConfigurationOptions.CommunicationControlIP, _communicationConfigurationOptions.CommunicationControlPort, - _communicationConfigurationOptions.CommunicationControlPath, request); - _logger.LogInformation($"Connect result for HiveID: {request.HiveID}: {connectResult}"); + var httpClient = _httpClientFactory.CreateClient("HiveConnectClient"); - if (connectResult != null) + var uriBuilder = new UriBuilder { - var hiveConnectResponse = JsonSerializer.Deserialize(connectResult); + Scheme = _communicationConfigurationOptions.RequestSchema, + Host = _communicationConfigurationOptions.CommunicationControlIP, + Port = _communicationConfigurationOptions.CommunicationControlPort, + Path = $"{_communicationConfigurationOptions.CommunicationControlPath}/connect" + }; + var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); + + var retryPolicy = Policy.HandleResult(r => !r.IsSuccessStatusCode) + .WaitAndRetryAsync( + 10, + retryAttempt => TimeSpan.FromSeconds(2), + (result, timeSpan, retryCount, context) => + { + _logger.LogWarning($"Connecting HiveID: {_communicationConfigurationOptions.HiveID}, retry attempt: {retryCount}. \nRequest URL: {uriBuilder.Uri}, request content: {jsonContent}"); + }); + + var response = await retryPolicy.ExecuteAsync(() => httpClient.PostAsync(uriBuilder.Uri, jsonContent)); + + if (response.IsSuccessStatusCode) + { + var connectResponse = await response.Content.ReadAsStringAsync(); + var hiveConnectResponse = JsonSerializer.Deserialize(connectResponse); if (hiveConnectResponse != null && hiveConnectResponse.ConnectResult) { @@ -58,10 +79,10 @@ namespace DevOpsProject.HiveMind.Logic.Services } else { - _logger.LogError($"Unable to connect Hive with ID: {request.HiveID}, Port: {request.HivePort}, IP: {request.HiveIP} to Communication Control. \n" + - $"Requested IP: {_communicationConfigurationOptions.CommunicationControlIP}, Port: {_communicationConfigurationOptions.HivePort}"); - throw new Exception($"Failed to connect hive for HiveID: {request.HiveID}"); + _logger.LogError($"Failed to connect hive, terminating process"); + System.Diagnostics.Process.GetCurrentProcess().Kill(); } + } public void StopAllTelemetry() @@ -103,8 +124,8 @@ namespace DevOpsProject.HiveMind.Logic.Services State = Shared.Enums.State.Move }; - var connectResult = await _httpClient.SendCommunicationControlTelemetryAsync(_communicationConfigurationOptions.RequestSchema, - _communicationConfigurationOptions.CommunicationControlIP, _communicationConfigurationOptions.CommunicationControlPort, + var connectResult = await _httpClient.SendCommunicationControlTelemetryAsync(_communicationConfigurationOptions.RequestSchema, + _communicationConfigurationOptions.CommunicationControlIP, _communicationConfigurationOptions.CommunicationControlPort, _communicationConfigurationOptions.CommunicationControlPath, request); _logger.LogInformation($"Telemetry sent for HiveID: {request.HiveID}: {connectResult}"); diff --git a/src/CommunicationControl/DevOpsProject/appsettings.json b/src/CommunicationControl/DevOpsProject/appsettings.json index 602dbc5..16d1344 100644 --- a/src/CommunicationControl/DevOpsProject/appsettings.json +++ b/src/CommunicationControl/DevOpsProject/appsettings.json @@ -1,4 +1,27 @@ { + "Redis": { + "ConnectionString": "localhost:6379", + "PublishChannel": "HiveChannel" + }, + "RedisKeys": { + "HiveKey": "Hive" + }, + "OperationalArea": { + "Latitude": 48.697189, + "Longitude": 38.066246, + "Radius_KM": 5, + "InitialHeight_KM": 1, + "InitialSpeed_KM": 5, + "TelemetryInterval_MS": 30000, + "PingInterval_MS": 15000 + + }, + "CommunicationConfiguration": { + "RequestScheme": "http", + "HiveMindPath": "api/v1" + }, + "AllowedHosts": "*", + "Urls": "http://0.0.0.0:8080", "Serilog": { "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "MinimumLevel": { @@ -31,28 +54,5 @@ "Application": "DevOpsProject.CommunicationControl", "Environment": "Development" } - }, - "Redis": { - "ConnectionString": "localhost:6379", - "PublishChannel": "HiveChannel" - }, - "RedisKeys": { - "HiveKey": "Hive" - }, - "OperationalArea": { - "Latitude": 48.697189, - "Longitude": 38.066246, - "Radius_KM": 5, - "InitialHeight_KM": 1, - "InitialSpeed_KM": 5, - "TelemetryInterval_MS": 30000, - "PingInterval_MS": 15000 - - }, - "CommunicationConfiguration": { - "RequestScheme": "http", - "HiveMindPath": "api/v1" - }, - "AllowedHosts": "*", - "Urls": "http://0.0.0.0:8080" + } }