From 63f03b56224280371f2c7cb4cdc2cff0427bb808 Mon Sep 17 00:00:00 2001 From: shaed-parkar <41630528+shaed-parkar@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:11:09 +0100 Subject: [PATCH] VIH-10173 refactor health checks to match POD requirements (#1272) --- .../packages.lock.json | 37 ++++ .../packages.lock.json | 37 ++++ .../Controllers/HealthTests.cs | 200 ------------------ .../AdminWebsite.UnitTests/packages.lock.json | 93 +++++--- AdminWebsite/AdminWebsite/AdminWebsite.csproj | 1 + .../Controllers/HealthCheckController.cs | 151 ------------- .../Health/HealthCheckExtensions.cs | 87 ++++++++ .../Models/HealthCheckResponse.cs | 34 --- AdminWebsite/AdminWebsite/Startup.cs | 21 +- AdminWebsite/AdminWebsite/packages.lock.json | 93 +++++--- charts/vh-admin-web/Chart.yaml | 2 +- charts/vh-admin-web/values.yaml | 5 +- 12 files changed, 316 insertions(+), 445 deletions(-) delete mode 100644 AdminWebsite/AdminWebsite.UnitTests/Controllers/HealthTests.cs delete mode 100644 AdminWebsite/AdminWebsite/Controllers/HealthCheckController.cs create mode 100644 AdminWebsite/AdminWebsite/Health/HealthCheckExtensions.cs delete mode 100644 AdminWebsite/AdminWebsite/Models/HealthCheckResponse.cs diff --git a/AdminWebsite/AdminWebsite.AcceptanceTests/packages.lock.json b/AdminWebsite/AdminWebsite.AcceptanceTests/packages.lock.json index 1401f8966..5188b3d0c 100644 --- a/AdminWebsite/AdminWebsite.AcceptanceTests/packages.lock.json +++ b/AdminWebsite/AdminWebsite.AcceptanceTests/packages.lock.json @@ -113,6 +113,15 @@ "System.Drawing.Common": "4.5.1" } }, + "AspNetCore.HealthChecks.Uris": { + "type": "Transitive", + "resolved": "6.0.3", + "contentHash": "EY0Vh8s2UrbnyvM/QhbyYuCnbrBw36BKkdh5LqdINxqAGnlPFQXf+/UoNlH/76MTEyg+nvdp2wjr5MqWDkVFaQ==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks": "6.0.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, "Azure.Core": { "type": "Transitive", "resolved": "1.25.0", @@ -822,6 +831,22 @@ "System.Text.Json": "6.0.0" } }, + "Microsoft.Extensions.Diagnostics.HealthChecks": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "ktOGFY2uJ6QqZbLgLZgYg6qWuOnwKEIYbpgGDR/1QY8E+8NhnL75dJZ+WDl88h7Q4JkIFeTkFBUGF5QmNcfUEg==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "6.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "6C9uhsA7GwT1qlXF+1JgOktilrWBSMLNVcwIjg63UvFaDVizg8fYTv826MC58dznvvT9yG31gGwsN1cFfg+yZQ==" + }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "7.0.0", @@ -883,6 +908,17 @@ "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0" } }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "6.0.0", @@ -2632,6 +2668,7 @@ "adminwebsite": { "type": "Project", "dependencies": { + "AspNetCore.HealthChecks.Uris": "[6.0.3, )", "BookingsApi.Client": "[1.44.57, )", "FluentValidation.AspNetCore": "[10.4.0, )", "LaunchDarkly.ServerSdk": "[7.0.3, )", diff --git a/AdminWebsite/AdminWebsite.IntegrationTests/packages.lock.json b/AdminWebsite/AdminWebsite.IntegrationTests/packages.lock.json index 7c448a41c..33b8b08a4 100644 --- a/AdminWebsite/AdminWebsite.IntegrationTests/packages.lock.json +++ b/AdminWebsite/AdminWebsite.IntegrationTests/packages.lock.json @@ -91,6 +91,15 @@ "resolved": "8.54.0.64047", "contentHash": "wqDNtVY4UDRximKGZ+XmDL19F2zjaCgn4r+j4VmyVL1iBYtbkQmYOiijrkHjzVS2QgTkdDhalsJ+6DUqjBfVhw==" }, + "AspNetCore.HealthChecks.Uris": { + "type": "Transitive", + "resolved": "6.0.3", + "contentHash": "EY0Vh8s2UrbnyvM/QhbyYuCnbrBw36BKkdh5LqdINxqAGnlPFQXf+/UoNlH/76MTEyg+nvdp2wjr5MqWDkVFaQ==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks": "6.0.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, "BookingsApi.Client": { "type": "Transitive", "resolved": "1.44.57", @@ -676,6 +685,22 @@ "System.Text.Json": "6.0.0" } }, + "Microsoft.Extensions.Diagnostics.HealthChecks": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "ktOGFY2uJ6QqZbLgLZgYg6qWuOnwKEIYbpgGDR/1QY8E+8NhnL75dJZ+WDl88h7Q4JkIFeTkFBUGF5QmNcfUEg==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "6.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "6C9uhsA7GwT1qlXF+1JgOktilrWBSMLNVcwIjg63UvFaDVizg8fYTv826MC58dznvvT9yG31gGwsN1cFfg+yZQ==" + }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "6.0.0", @@ -737,6 +762,17 @@ "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0" } }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "6.0.0", @@ -2064,6 +2100,7 @@ "adminwebsite": { "type": "Project", "dependencies": { + "AspNetCore.HealthChecks.Uris": "[6.0.3, )", "BookingsApi.Client": "[1.44.57, )", "FluentValidation.AspNetCore": "[10.4.0, )", "LaunchDarkly.ServerSdk": "[7.0.3, )", diff --git a/AdminWebsite/AdminWebsite.UnitTests/Controllers/HealthTests.cs b/AdminWebsite/AdminWebsite.UnitTests/Controllers/HealthTests.cs deleted file mode 100644 index 3c0dfd3c0..000000000 --- a/AdminWebsite/AdminWebsite.UnitTests/Controllers/HealthTests.cs +++ /dev/null @@ -1,200 +0,0 @@ -using AdminWebsite.Controllers; -using FizzWare.NBuilder; -using FluentAssertions; -using Microsoft.AspNetCore.Mvc; -using Moq; -using NUnit.Framework; -using System; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using BookingsApi.Client; -using HealthCheckResponse = AdminWebsite.Models.HealthCheckResponse; -using NotificationApi.Client; -using NotificationApi.Contract; -using Microsoft.Extensions.Logging; -using UserApi.Client; -using UserApi.Contract.Responses; -using VideoApi.Client; - -namespace AdminWebsite.UnitTests.Controllers -{ - public class HealthTests - { - private HealthCheckController _controller; - private Mock _userApiClientMock; - private Mock _bookingsApiClientMock; - private Mock _videoApiClientMock; - private Mock _notificationApiClientMock; - - [SetUp] - public void Setup() - { - _userApiClientMock = new Mock(); - _bookingsApiClientMock = new Mock(); - _videoApiClientMock = new Mock(); - _notificationApiClientMock = new Mock(); - - _controller = new HealthCheckController(_userApiClientMock.Object, _bookingsApiClientMock.Object, _videoApiClientMock.Object, - _notificationApiClientMock.Object, new Mock>().Object); - - var judges = Builder.CreateListOfSize(3).Build().ToList(); - - _userApiClientMock.Setup(x => x.GetJudgesAsync()).ReturnsAsync(judges); - } - - [Test] - public async Task Should_return_internal_server_error_result_when_user_api_not_reachable() - { - var exception = new AggregateException("kinly api error"); - - _userApiClientMock - .Setup(x => x.GetJudgesAsync()) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(HttpStatusCode.InternalServerError); - response.UserApiHealth.Successful.Should().BeFalse(); - response.UserApiHealth.ErrorMessage.Should().NotBeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_user_api_exception_when_user_api_not_found() - { - var exception = new UserApiException("User api error", 404, "response", null, new Exception()); - - _userApiClientMock - .Setup(x => x.GetJudgesAsync()) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(); - response.UserApiHealth.Successful.Should().BeTrue(); - response.UserApiHealth.ErrorMessage.Should().BeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_internal_server_error_result_when_bookings_api_not_reachable() - { - var exception = new AggregateException("kinly api error"); - - _bookingsApiClientMock - .Setup(x => x.GetCaseTypesAsync(It.IsAny())) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(HttpStatusCode.InternalServerError); - response.BookingsApiHealth.Successful.Should().BeFalse(); - response.BookingsApiHealth.ErrorMessage.Should().NotBeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_internal_server_error_result_when_non_booking_api_exception_thrown() - { - var exception = new UriFormatException("Test format is invalid"); - - _bookingsApiClientMock - .Setup(x => x.GetCaseTypesAsync(It.IsAny())) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(HttpStatusCode.InternalServerError); - response.BookingsApiHealth.Successful.Should().BeFalse(); - response.BookingsApiHealth.ErrorMessage.Should().NotBeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_booking_api_exception_when_booking_api_not_found() - { - var exception = new BookingsApiException("Bookings api error", 404, "response", null, new Exception()); - - _bookingsApiClientMock - .Setup(x => x.GetCaseTypesAsync(It.IsAny())) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(); - response.BookingsApiHealth.Successful.Should().BeTrue(); - response.BookingsApiHealth.ErrorMessage.Should().BeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_internal_server_error_result_when_video_api_not_reachable() - { - var exception = new AggregateException("kinly api error"); - - _videoApiClientMock - .Setup(x => x.GetExpiredOpenConferencesAsync()) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(HttpStatusCode.InternalServerError); - response.VideoApiHealth.Successful.Should().BeFalse(); - response.VideoApiHealth.ErrorMessage.Should().NotBeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_video_api_exception_when_video_api_not_found() - { - var exception = new VideoApiException("Video api error", 404, "response", null, new Exception()); - - _videoApiClientMock - .Setup(x => x.GetExpiredOpenConferencesAsync()) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(); - response.VideoApiHealth.Successful.Should().BeTrue(); - response.VideoApiHealth.ErrorMessage.Should().BeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_internal_server_error_result_when_notification_api_not_reachable() - { - var exception = new AggregateException("Notification api error"); - - _notificationApiClientMock - .Setup(x => x.GetTemplateByNotificationTypeAsync(It.IsAny())) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(HttpStatusCode.InternalServerError); - response.NotificationApiHealth.Successful.Should().BeFalse(); - response.NotificationApiHealth.ErrorMessage.Should().NotBeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_notify_exception_when_notification_api_not_found() - { - var exception = new NotificationApiException("Notification api error", 404, "response", null, new Exception()); - - _notificationApiClientMock - .Setup(x => x.GetTemplateByNotificationTypeAsync(It.IsAny())) - .ThrowsAsync(exception); - - var response = await GetResponseFromHealthCheck(); - response.NotificationApiHealth.Successful.Should().BeTrue(); - response.NotificationApiHealth.ErrorMessage.Should().BeNullOrWhiteSpace(); - } - - [Test] - public async Task Should_return_ok_when_all_services_are_running() - { - var response = await GetResponseFromHealthCheck(); - response.BookingsApiHealth.Successful.Should().BeTrue(); - response.UserApiHealth.Successful.Should().BeTrue(); - response.VideoApiHealth.Successful.Should().BeTrue(); - response.NotificationApiHealth.Successful.Should().BeTrue(); - } - - [Test] - public async Task Should_return_the_application_version_from_assembly() - { - var result = await _controller.Health(); - var typedResult = (ObjectResult)result; - var response = (HealthCheckResponse)typedResult.Value; - response.AppVersion.FileVersion.Should().NotBeNullOrEmpty(); - response.AppVersion.InformationVersion.Should().NotBeNullOrEmpty(); - } - - private async Task GetResponseFromHealthCheck(HttpStatusCode expectedStatusCode = HttpStatusCode.OK) - { - var result = await _controller.Health(); - var typedResult = (ObjectResult)result; - typedResult.StatusCode.Should().Be((int)expectedStatusCode); - return (HealthCheckResponse)typedResult.Value; - } - } -} diff --git a/AdminWebsite/AdminWebsite.UnitTests/packages.lock.json b/AdminWebsite/AdminWebsite.UnitTests/packages.lock.json index a3bbb3f20..ed9c84a80 100644 --- a/AdminWebsite/AdminWebsite.UnitTests/packages.lock.json +++ b/AdminWebsite/AdminWebsite.UnitTests/packages.lock.json @@ -96,6 +96,15 @@ "Microsoft.AspNetCore.Mvc.Core": "2.2.5" } }, + "AspNetCore.HealthChecks.Uris": { + "type": "Transitive", + "resolved": "6.0.3", + "contentHash": "EY0Vh8s2UrbnyvM/QhbyYuCnbrBw36BKkdh5LqdINxqAGnlPFQXf+/UoNlH/76MTEyg+nvdp2wjr5MqWDkVFaQ==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks": "6.0.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, "BookingsApi.Client": { "type": "Transitive", "resolved": "1.44.57", @@ -582,10 +591,10 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "3.1.11", - "contentHash": "vdGEMIhzMGPgeb4WduWqJjgWK+WpkcLwssHJ5zKHQgqAaUdwbaMzMFz8bpOPkRfzwDVPomXmZChUh4fgpuxUjQ==", + "resolved": "6.0.0", + "contentHash": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.11" + "Microsoft.Extensions.Primitives": "6.0.0" } }, "Microsoft.Extensions.Configuration.Binder": { @@ -633,16 +642,17 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "Jg48cM12C8iqjpFMNXWEmJR/2jnmScV8Y8txVOayycLqHatLV2Cn4wuwj56KSOc/xVWwlWmq9Y5ebDH6awnexw==", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "uKZr5b4w9nQE6UAXS4PqBFbg2qe6kS+pdsCbDG2kHjUaWT/B7EOcDraoR52H/4BoBIEH3By5co6Wioru7+v9Zg==" + "resolved": "6.0.0", + "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -652,6 +662,22 @@ "System.Text.Json": "4.7.2" } }, + "Microsoft.Extensions.Diagnostics.HealthChecks": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "ktOGFY2uJ6QqZbLgLZgYg6qWuOnwKEIYbpgGDR/1QY8E+8NhnL75dJZ+WDl88h7Q4JkIFeTkFBUGF5QmNcfUEg==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "6.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "6C9uhsA7GwT1qlXF+1JgOktilrWBSMLNVcwIjg63UvFaDVizg8fYTv826MC58dznvvT9yG31gGwsN1cFfg+yZQ==" + }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "6.0.0", @@ -677,24 +703,35 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "2.2.0", - "contentHash": "+k4AEn68HOJat5gj1TWa6X28WlirNQO9sPIIeQbia+91n03esEtMSSoekSTpMjUzjqtJWQN3McVx0GvSPFHF/Q==", + "resolved": "6.0.0", + "contentHash": "GcT5l2CYXL6Sa27KCSh0TixsRfADUgth+ojQSD5EkzisZxmGFh7CwzkcYuGwvmXLjr27uWRNrJ2vuuEjMhU05Q==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "2.2.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", - "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", - "Microsoft.Extensions.Logging.Abstractions": "2.2.0" + "Microsoft.Extensions.Configuration.Abstractions": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" } }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "SykGku44CIQJRM+eUzB61bGVNv0Lmgx70UAmhu1XLFXhP4I0rBSNJusfOlEpDN8T5JQsykIuM2lOd8J8nTnfAg==", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "3.1.1", - "Microsoft.Extensions.DependencyInjection": "3.1.1", - "Microsoft.Extensions.Logging.Abstractions": "3.1.1", - "Microsoft.Extensions.Options": "3.1.1" + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0", + "System.Diagnostics.DiagnosticSource": "6.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { @@ -737,11 +774,11 @@ }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "p2faCNhzXyG5oLLOV8n8dwg2rfqS4aRpIWu4qgwZMq+al3133mAQ+Hb822iwRoj3qnKM8zY4A6Jz/Vm/xWHvrA==", + "resolved": "6.0.0", + "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.1", - "Microsoft.Extensions.Primitives": "3.1.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { @@ -1227,8 +1264,11 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" + "resolved": "6.0.0", + "contentHash": "frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } }, "System.Diagnostics.PerformanceCounter": { "type": "Transitive", @@ -1938,6 +1978,7 @@ "adminwebsite": { "type": "Project", "dependencies": { + "AspNetCore.HealthChecks.Uris": "[6.0.3, )", "BookingsApi.Client": "[1.44.57, )", "FluentValidation.AspNetCore": "[10.4.0, )", "LaunchDarkly.ServerSdk": "[7.0.3, )", diff --git a/AdminWebsite/AdminWebsite/AdminWebsite.csproj b/AdminWebsite/AdminWebsite/AdminWebsite.csproj index 3791be11f..6b11a5b02 100644 --- a/AdminWebsite/AdminWebsite/AdminWebsite.csproj +++ b/AdminWebsite/AdminWebsite/AdminWebsite.csproj @@ -31,6 +31,7 @@ false + diff --git a/AdminWebsite/AdminWebsite/Controllers/HealthCheckController.cs b/AdminWebsite/AdminWebsite/Controllers/HealthCheckController.cs deleted file mode 100644 index e2aaa4fdc..000000000 --- a/AdminWebsite/AdminWebsite/Controllers/HealthCheckController.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Swashbuckle.AspNetCore.Annotations; -using System; -using System.Net; -using System.Reflection; -using System.Threading.Tasks; -using BookingsApi.Client; -using HealthCheckResponse = AdminWebsite.Models.HealthCheckResponse; -using NotificationApi.Client; -using NotificationApi.Contract; -using Microsoft.Extensions.Logging; -using UserApi.Client; -using VideoApi.Client; - -namespace AdminWebsite.Controllers -{ - [Produces("application/json")] - [AllowAnonymous] - [ApiController] - public class HealthCheckController : Controller - { - private readonly IUserApiClient _userApiClient; - private readonly IBookingsApiClient _bookingsApiClient; - private readonly IVideoApiClient _videoApiClient; - private readonly INotificationApiClient _notificationApiClient; - private readonly ILogger _logger; - - public HealthCheckController(IUserApiClient userApiClient, IBookingsApiClient bookingsApiClient, - IVideoApiClient videoApiClient, INotificationApiClient notificationApiClient, - ILogger logger) - { - _userApiClient = userApiClient; - _bookingsApiClient = bookingsApiClient; - _videoApiClient = videoApiClient; - _notificationApiClient = notificationApiClient; - _logger = logger; - } - - /// - /// Check Service Health - /// - /// Error if fails, otherwise OK status - [HttpGet("HealthCheck/health")] - [HttpGet("health/liveness")] - [SwaggerOperation(OperationId = "CheckServiceHealth")] - [ProducesResponseType(typeof(HealthCheckResponse), (int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(HealthCheckResponse), (int)HttpStatusCode.InternalServerError)] - public async Task Health() - { - var response = new HealthCheckResponse - { - BookingsApiHealth = { Successful = true }, - UserApiHealth = { Successful = true }, - VideoApiHealth = { Successful = true }, - NotificationApiHealth = { Successful = true }, - AppVersion = GetApplicationVersion() - }; - try - { - await _userApiClient.GetJudgesAsync(); - } - catch (UserApiException uaEx) - { - _logger.LogError(uaEx, "There was a problem getting judgelist from UserAPI on health check. Status Code {StatusCode} - Message {Message}", - uaEx.StatusCode, uaEx.Response); - } - catch (Exception ex) - { - response.UserApiHealth.Successful = false; - response.UserApiHealth.ErrorMessage = ex.Message; - response.UserApiHealth.Data = ex.Data; - } - - try - { - await _bookingsApiClient.GetCaseTypesAsync(includeDeleted:false); - } - catch (BookingsApiException baEx) - { - _logger.LogError(baEx, "There was a problem getting casetypes from BookigAPI on health check. Status Code {StatusCode} - Message {Message}", - baEx.StatusCode, baEx.Response); - } - catch (Exception ex) - { - response.BookingsApiHealth.Successful = false; - response.BookingsApiHealth.ErrorMessage = ex.Message; - response.BookingsApiHealth.Data = ex.Data; - } - - try - { - await _videoApiClient.GetExpiredOpenConferencesAsync(); - } - catch (VideoApiException baEx) - { - _logger.LogError(baEx, "There was a problem getting expiered open conferences from VideoApi on health check. " + - "Status Code {StatusCode} - Message {Message}", - baEx.StatusCode, baEx.Response); - } - catch (Exception ex) - { - response.VideoApiHealth.Successful = false; - response.VideoApiHealth.ErrorMessage = ex.Message; - response.VideoApiHealth.Data = ex.Data; - } - - try - { - await _notificationApiClient.GetTemplateByNotificationTypeAsync(NotificationType.CreateIndividual); - } - catch (NotificationApiException naEx) - { - _logger.LogError(naEx, "There was a problem getting templates on health check. Status Code {StatusCode} - Message {Message}", - naEx.StatusCode, naEx.Response); - } - catch (Exception ex) - { - response.NotificationApiHealth.Successful = false; - response.NotificationApiHealth.ErrorMessage = ex.Message; - response.NotificationApiHealth.Data = ex.Data; - } - - if (!response.UserApiHealth.Successful || !response.BookingsApiHealth.Successful || !response.VideoApiHealth.Successful - || !response.NotificationApiHealth.Successful) - { - return StatusCode((int)HttpStatusCode.InternalServerError, response); - } - - return Ok(response); - } - - private Models.ApplicationVersion GetApplicationVersion() - { - var applicationVersion = new Models.ApplicationVersion() - { - FileVersion = GetExecutingAssemblyAttribute(a => a.Version), - InformationVersion = GetExecutingAssemblyAttribute(a => a.InformationalVersion) - }; - - return applicationVersion; - } - - private static string GetExecutingAssemblyAttribute(Func value) where T : Attribute - { - var attribute = (T)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(T)); - - return value.Invoke(attribute); - } - } -} \ No newline at end of file diff --git a/AdminWebsite/AdminWebsite/Health/HealthCheckExtensions.cs b/AdminWebsite/AdminWebsite/Health/HealthCheckExtensions.cs new file mode 100644 index 000000000..5f3c79073 --- /dev/null +++ b/AdminWebsite/AdminWebsite/Health/HealthCheckExtensions.cs @@ -0,0 +1,87 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using AdminWebsite.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace AdminWebsite.Health; + +[ExcludeFromCodeCoverage] +public static class HealthCheckExtensions +{ + public static IServiceCollection AddVhHealthChecks(this IServiceCollection services) + { + var container = services.BuildServiceProvider(); + var servicesConfiguration = container.GetService>().Value; + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup( + new Uri( + new Uri(servicesConfiguration.VideoApiUrl), + "/health/liveness"), + name: "Video API", + failureStatus: HealthStatus.Unhealthy, + tags: new[] {"startup", "readiness"}) + .AddUrlGroup( + new Uri( + new Uri(servicesConfiguration.BookingsApiUrl), + "/health/liveness"), + name: "Bookings API", + failureStatus: HealthStatus.Unhealthy, + tags: new[] {"startup", "readiness"}) + .AddUrlGroup( + new Uri( + new Uri(servicesConfiguration.UserApiUrl), + "/health/liveness"), + name: "User API", + failureStatus: HealthStatus.Unhealthy, + tags: new[] {"startup", "readiness"}); + return services; + } + + public static IEndpointRouteBuilder AddVhHealthCheckRouteMaps(this IEndpointRouteBuilder endpoints) + { + endpoints.MapHealthChecks("/health/liveness", new HealthCheckOptions() + { + Predicate = check => check.Tags.Contains("self"), + ResponseWriter = HealthCheckResponseWriter + }); + + endpoints.MapHealthChecks("/health/startup", new HealthCheckOptions() + { + Predicate = check => check.Tags.Contains("startup"), + ResponseWriter = HealthCheckResponseWriter + }); + + endpoints.MapHealthChecks("/health/readiness", new HealthCheckOptions() + { + Predicate = check => check.Tags.Contains("readiness"), + ResponseWriter = HealthCheckResponseWriter + }); + + return endpoints; + } + + private static async Task HealthCheckResponseWriter(HttpContext context, HealthReport report) + { + var result = JsonConvert.SerializeObject(new + { + status = report.Status.ToString(), + details = report.Entries.Select(e => new + { + key = e.Key, value = Enum.GetName(typeof(HealthStatus), e.Value.Status), + error = e.Value.Exception?.Message + }) + }); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(result); + } +} diff --git a/AdminWebsite/AdminWebsite/Models/HealthCheckResponse.cs b/AdminWebsite/AdminWebsite/Models/HealthCheckResponse.cs deleted file mode 100644 index 6c24ed736..000000000 --- a/AdminWebsite/AdminWebsite/Models/HealthCheckResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections; - -namespace AdminWebsite.Models -{ - public class HealthCheckResponse - { - public HealthCheckResponse() - { - BookingsApiHealth = new HealthCheck(); - UserApiHealth = new HealthCheck(); - VideoApiHealth = new HealthCheck(); - NotificationApiHealth = new HealthCheck(); - AppVersion = new ApplicationVersion(); - } - public HealthCheck BookingsApiHealth { get; set; } - public HealthCheck UserApiHealth { get; set; } - public HealthCheck VideoApiHealth { get; set; } - public HealthCheck NotificationApiHealth { get; set; } - public ApplicationVersion AppVersion { get; set; } - } - - public class HealthCheck - { - public bool Successful { get; set; } - public string ErrorMessage { get; set; } - public IDictionary Data { get; set; } - } - - public class ApplicationVersion - { - public string FileVersion { get; set; } - public string InformationVersion { get; set; } - } -} diff --git a/AdminWebsite/AdminWebsite/Startup.cs b/AdminWebsite/AdminWebsite/Startup.cs index 160907299..b4fc290af 100644 --- a/AdminWebsite/AdminWebsite/Startup.cs +++ b/AdminWebsite/AdminWebsite/Startup.cs @@ -2,6 +2,7 @@ using AdminWebsite.Configuration; using AdminWebsite.Contracts.Responses; using AdminWebsite.Extensions; +using AdminWebsite.Health; using AdminWebsite.Middleware; using AdminWebsite.Services; using FluentValidation.AspNetCore; @@ -45,6 +46,8 @@ public void ConfigureServices(IServiceCollection services) services.AddCustomTypes(); + services.AddVhHealthChecks(); + services.RegisterAuthSchemes(Configuration); services.AddMvc(opt => { @@ -59,11 +62,13 @@ private void RegisterSettings(IServiceCollection services) { Settings = Configuration.Get(); - services.Configure(options => Configuration.Bind(Dom1AdConfiguration.ConfigSectionKey, options)); + services.Configure(options => + Configuration.Bind(Dom1AdConfiguration.ConfigSectionKey, options)); services.Configure(options => Configuration.Bind("AzureAd", options)); services.Configure(options => Configuration.Bind("VhServices", options)); services.Configure(options => Configuration.Bind("KinlyConfiguration", options)); - services.Configure(options => Configuration.Bind("ApplicationInsights", options)); + services.Configure(options => + Configuration.Bind("ApplicationInsights", options)); services.Configure(options => Configuration.Bind("TestUserSecrets", options)); } @@ -118,13 +123,19 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHsts(); // this is a workaround to set HSTS in a docker // reference from https://github.com/dotnet/dotnet-docker/issues/2268#issuecomment-714613811 - app.Use(async (context, next) => { + app.Use(async (context, next) => + { context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000"); await next.Invoke(); }); app.UseXfo(options => options.SameOrigin()); - app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + + endpoints.AddVhHealthCheckRouteMaps(); + }); app.UseSpa(spa => { @@ -140,5 +151,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } }); } + + } } diff --git a/AdminWebsite/AdminWebsite/packages.lock.json b/AdminWebsite/AdminWebsite/packages.lock.json index 6681fd739..6757074aa 100644 --- a/AdminWebsite/AdminWebsite/packages.lock.json +++ b/AdminWebsite/AdminWebsite/packages.lock.json @@ -2,6 +2,16 @@ "version": 1, "dependencies": { "net6.0": { + "AspNetCore.HealthChecks.Uris": { + "type": "Direct", + "requested": "[6.0.3, )", + "resolved": "6.0.3", + "contentHash": "EY0Vh8s2UrbnyvM/QhbyYuCnbrBw36BKkdh5LqdINxqAGnlPFQXf+/UoNlH/76MTEyg+nvdp2wjr5MqWDkVFaQ==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks": "6.0.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, "BookingsApi.Client": { "type": "Direct", "requested": "[1.44.57, )", @@ -615,10 +625,10 @@ }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "3.1.11", - "contentHash": "vdGEMIhzMGPgeb4WduWqJjgWK+WpkcLwssHJ5zKHQgqAaUdwbaMzMFz8bpOPkRfzwDVPomXmZChUh4fgpuxUjQ==", + "resolved": "6.0.0", + "contentHash": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.11" + "Microsoft.Extensions.Primitives": "6.0.0" } }, "Microsoft.Extensions.Configuration.Binder": { @@ -666,16 +676,17 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "Jg48cM12C8iqjpFMNXWEmJR/2jnmScV8Y8txVOayycLqHatLV2Cn4wuwj56KSOc/xVWwlWmq9Y5ebDH6awnexw==", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "uKZr5b4w9nQE6UAXS4PqBFbg2qe6kS+pdsCbDG2kHjUaWT/B7EOcDraoR52H/4BoBIEH3By5co6Wioru7+v9Zg==" + "resolved": "6.0.0", + "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -685,6 +696,22 @@ "System.Text.Json": "4.7.2" } }, + "Microsoft.Extensions.Diagnostics.HealthChecks": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "ktOGFY2uJ6QqZbLgLZgYg6qWuOnwKEIYbpgGDR/1QY8E+8NhnL75dJZ+WDl88h7Q4JkIFeTkFBUGF5QmNcfUEg==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "6.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "6C9uhsA7GwT1qlXF+1JgOktilrWBSMLNVcwIjg63UvFaDVizg8fYTv826MC58dznvvT9yG31gGwsN1cFfg+yZQ==" + }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "6.0.0", @@ -710,24 +737,35 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "2.2.0", - "contentHash": "+k4AEn68HOJat5gj1TWa6X28WlirNQO9sPIIeQbia+91n03esEtMSSoekSTpMjUzjqtJWQN3McVx0GvSPFHF/Q==", + "resolved": "6.0.0", + "contentHash": "GcT5l2CYXL6Sa27KCSh0TixsRfADUgth+ojQSD5EkzisZxmGFh7CwzkcYuGwvmXLjr27uWRNrJ2vuuEjMhU05Q==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "2.2.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", - "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", - "Microsoft.Extensions.Logging.Abstractions": "2.2.0" + "Microsoft.Extensions.Configuration.Abstractions": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" } }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "SykGku44CIQJRM+eUzB61bGVNv0Lmgx70UAmhu1XLFXhP4I0rBSNJusfOlEpDN8T5JQsykIuM2lOd8J8nTnfAg==", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "3.1.1", - "Microsoft.Extensions.DependencyInjection": "3.1.1", - "Microsoft.Extensions.Logging.Abstractions": "3.1.1", - "Microsoft.Extensions.Options": "3.1.1" + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0", + "System.Diagnostics.DiagnosticSource": "6.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { @@ -770,11 +808,11 @@ }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "p2faCNhzXyG5oLLOV8n8dwg2rfqS4aRpIWu4qgwZMq+al3133mAQ+Hb822iwRoj3qnKM8zY4A6Jz/Vm/xWHvrA==", + "resolved": "6.0.0", + "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.1", - "Microsoft.Extensions.Primitives": "3.1.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { @@ -1167,8 +1205,11 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" + "resolved": "6.0.0", + "contentHash": "frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } }, "System.Diagnostics.PerformanceCounter": { "type": "Transitive", diff --git a/charts/vh-admin-web/Chart.yaml b/charts/vh-admin-web/Chart.yaml index 69d814809..ee2e0e07a 100644 --- a/charts/vh-admin-web/Chart.yaml +++ b/charts/vh-admin-web/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: vh-admin-web home: https://github.com/hmcts/vh-admin-web -version: 0.0.22 +version: 0.0.23 description: Helm Chart for Admin Web maintainers: - name: VH Devops diff --git a/charts/vh-admin-web/values.yaml b/charts/vh-admin-web/values.yaml index a4896eff1..51a6a60c0 100644 --- a/charts/vh-admin-web/values.yaml +++ b/charts/vh-admin-web/values.yaml @@ -5,10 +5,9 @@ java: replicas: 2 ingressHost: vh-admin-web.{{ .Values.global.environment }}.platform.hmcts.net releaseNameOverride: vh-admin-web - readinessPath: /health/liveness livenessPath: /health/liveness - livenessDelay: 360 - livenessPeriod: 120 + readinessPath: /health/readiness + startupPath: /health/startup aadIdentityName: vh-aad-identity keyVaults: vh-infra-core: