diff --git a/Greentube.Monitoring.sln b/Greentube.Monitoring.sln index 16c0dd9..c373645 100644 --- a/Greentube.Monitoring.sln +++ b/Greentube.Monitoring.sln @@ -40,6 +40,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greentube.Monitoring.Apache EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greentube.Monitoring.IntegrationTests", "test\Greentube.Monitoring.IntegrationTests\Greentube.Monitoring.IntegrationTests.csproj", "{B9FAE3D9-FB45-479B-ADAE-5A81B0EED0A8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Greentube.Monitoring.InternalResource", "src\Greentube.Monitoring.InternalResource\Greentube.Monitoring.InternalResource.csproj", "{6C953298-9D57-49A7-8123-C110A99EADA6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Coverage|Any CPU = Coverage|Any CPU @@ -125,6 +127,12 @@ Global {B9FAE3D9-FB45-479B-ADAE-5A81B0EED0A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9FAE3D9-FB45-479B-ADAE-5A81B0EED0A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9FAE3D9-FB45-479B-ADAE-5A81B0EED0A8}.Release|Any CPU.Build.0 = Release|Any CPU + {6C953298-9D57-49A7-8123-C110A99EADA6}.Coverage|Any CPU.ActiveCfg = Debug|Any CPU + {6C953298-9D57-49A7-8123-C110A99EADA6}.Coverage|Any CPU.Build.0 = Debug|Any CPU + {6C953298-9D57-49A7-8123-C110A99EADA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C953298-9D57-49A7-8123-C110A99EADA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C953298-9D57-49A7-8123-C110A99EADA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C953298-9D57-49A7-8123-C110A99EADA6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -143,6 +151,7 @@ Global {D5DEE9C7-0A0A-4789-BD75-F78F5037C69A} = {843C8ED0-4792-40E0-8903-A3E5FF74A0A3} {DA5A72B9-EEED-4CFF-97A4-C5FE6BC90FB6} = {AE749C50-94C1-445C-B13F-E9D19372CBDB} {B9FAE3D9-FB45-479B-ADAE-5A81B0EED0A8} = {AE749C50-94C1-445C-B13F-E9D19372CBDB} + {6C953298-9D57-49A7-8123-C110A99EADA6} = {843C8ED0-4792-40E0-8903-A3E5FF74A0A3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BC80F7F7-4B53-4822-BFCB-50AC6E0EE384} diff --git a/src/Greentube.Monitoring.InternalResource/Greentube.Monitoring.InternalResource.csproj b/src/Greentube.Monitoring.InternalResource/Greentube.Monitoring.InternalResource.csproj new file mode 100644 index 0000000..2bbdcbb --- /dev/null +++ b/src/Greentube.Monitoring.InternalResource/Greentube.Monitoring.InternalResource.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + diff --git a/src/Greentube.Monitoring.InternalResource/IInternalResourceMonitored.cs b/src/Greentube.Monitoring.InternalResource/IInternalResourceMonitored.cs new file mode 100644 index 0000000..2e9edc7 --- /dev/null +++ b/src/Greentube.Monitoring.InternalResource/IInternalResourceMonitored.cs @@ -0,0 +1,16 @@ +namespace Greentube.Monitoring.InternalResource +{ + /// + /// Represents internal resource state + /// + public interface IInternalResourceMonitored + { + /// + /// Gets a value indicating whether this resource is up. + /// + /// + /// true if this instance is up; otherwise, false. + /// + bool IsUp { get; } + } +} \ No newline at end of file diff --git a/src/Greentube.Monitoring.InternalResource/InternalResourceHealthCheckStrategy.cs b/src/Greentube.Monitoring.InternalResource/InternalResourceHealthCheckStrategy.cs new file mode 100644 index 0000000..114635f --- /dev/null +++ b/src/Greentube.Monitoring.InternalResource/InternalResourceHealthCheckStrategy.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Greentube.Monitoring.InternalResource +{ + public sealed class InternalResourceHealthCheckStrategy : IHealthCheckStrategy + { + private readonly IInternalResourceMonitored _internalResourceMonitored; + + public InternalResourceHealthCheckStrategy(IInternalResourceMonitored internalResourceMonitored) + { + _internalResourceMonitored = internalResourceMonitored; + } + + public Task Check(CancellationToken token) + { + return Task.FromResult(_internalResourceMonitored.IsUp); + } + } +} diff --git a/src/Greentube.Monitoring.InternalResource/InternalResourceMonitor.cs b/src/Greentube.Monitoring.InternalResource/InternalResourceMonitor.cs new file mode 100644 index 0000000..31fd575 --- /dev/null +++ b/src/Greentube.Monitoring.InternalResource/InternalResourceMonitor.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Logging; + +namespace Greentube.Monitoring.InternalResource +{ + /// + /// Internal resource monitor + /// + /// + public sealed class InternalResourceMonitor : ResourceMonitor + { + public InternalResourceMonitor( + string resourceName, + InternalResourceHealthCheckStrategy verificationStrategy, + IResourceMonitorConfiguration configuration, + ILogger logger, + bool isCritical = false) + : base(resourceName, verificationStrategy, configuration, logger, isCritical) + { + } + } +} diff --git a/src/Greentube.Monitoring.InternalResource/InternalResourceMonitorExtensions.cs b/src/Greentube.Monitoring.InternalResource/InternalResourceMonitorExtensions.cs new file mode 100644 index 0000000..e9720ee --- /dev/null +++ b/src/Greentube.Monitoring.InternalResource/InternalResourceMonitorExtensions.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Greentube.Monitoring.InternalResource +{ + public static class InternalResourceMonitoringExtensions + { + /// + /// Adds the internal resource monitor. + /// + /// The options. + /// Instance of internal resource that has state + /// Name of the resource. Defaults to: Uri.AbsoluteUri. + /// if set to true [is critical]. + /// The configuration override. + public static void AddInternalResourceMonitor( + this MonitoringOptions options, + IInternalResourceMonitored internalResource, + string resourceName, + bool isCritical = false, + IResourceMonitorConfiguration configOverride = null) + { + options.AddResourceMonitor((conf, provider) => + { + var logger = provider.GetRequiredService>(); + return new InternalResourceMonitor( + resourceName, + new InternalResourceHealthCheckStrategy(internalResource), + configOverride ?? conf, + logger, isCritical + ); + }); + } + } +} \ No newline at end of file diff --git a/test/Greentube.Monitoring.IntegrationTests/Greentube.Monitoring.IntegrationTests.csproj b/test/Greentube.Monitoring.IntegrationTests/Greentube.Monitoring.IntegrationTests.csproj index cf708ea..76cf21e 100644 --- a/test/Greentube.Monitoring.IntegrationTests/Greentube.Monitoring.IntegrationTests.csproj +++ b/test/Greentube.Monitoring.IntegrationTests/Greentube.Monitoring.IntegrationTests.csproj @@ -19,6 +19,7 @@ + diff --git a/test/Greentube.Monitoring.IntegrationTests/InternalResourceMonitorTests.cs b/test/Greentube.Monitoring.IntegrationTests/InternalResourceMonitorTests.cs new file mode 100644 index 0000000..3af63f9 --- /dev/null +++ b/test/Greentube.Monitoring.IntegrationTests/InternalResourceMonitorTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Xunit; + +namespace Greentube.Monitoring.IntegrationTests +{ + public class InternalResourceMonitorTests + { + private readonly TestServer _server; + + public InternalResourceMonitorTests() + { + var webHostBuilder = new WebHostBuilder(); + _server = new TestServer(webHostBuilder.UseStartup()); + } + + [Fact] + public async Task InternalStateIsMonitored() + { + var client = _server.CreateClient(); + + // check if node is up + var healthCheckResponse = await GetHealth(client); + Assert.True(healthCheckResponse.Up); + + // set internalState to down + await client.GetAsync("/?SetToDown"); + + await Task.Delay(TimeSpan.FromMilliseconds(500)); + // check if node is down + healthCheckResponse = await GetHealth(client); + Assert.False(healthCheckResponse.Up); + + // set internalState to down + await client.GetAsync("/?SetToUp"); + + await Task.Delay(TimeSpan.FromMilliseconds(500)); + // check if node is up again + healthCheckResponse = await GetHealth(client); + Assert.True(healthCheckResponse.Up); + } + + private static async Task GetHealth(HttpClient client) + { + var httpResponseMessage = await client.GetAsync("/health?Detailed"); + var content = await httpResponseMessage.Content.ReadAsStringAsync(); + var healthCheckResponse = + JsonConvert.DeserializeObject(content); + return healthCheckResponse; + } + + private class HealthCheckResponse + { + public bool Up { get; set; } + } + } +} \ No newline at end of file diff --git a/test/Greentube.Monitoring.IntegrationTests/SimpleInternalResource.cs b/test/Greentube.Monitoring.IntegrationTests/SimpleInternalResource.cs new file mode 100644 index 0000000..730b83b --- /dev/null +++ b/test/Greentube.Monitoring.IntegrationTests/SimpleInternalResource.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Greentube.Monitoring.InternalResource; + +namespace Greentube.Monitoring.IntegrationTests +{ + class SimpleInternalResource: IInternalResourceMonitored + { + public bool IsUp { get; set; } = false; + } +} diff --git a/test/Greentube.Monitoring.IntegrationTests/TestStartup.cs b/test/Greentube.Monitoring.IntegrationTests/TestStartup.cs index c69156a..1ec90a3 100644 --- a/test/Greentube.Monitoring.IntegrationTests/TestStartup.cs +++ b/test/Greentube.Monitoring.IntegrationTests/TestStartup.cs @@ -1,19 +1,24 @@ using System; using System.Reflection; using Greentube.Monitoring.DependencyInjection; +using Greentube.Monitoring.InternalResource; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; + namespace Greentube.Monitoring.IntegrationTests { public sealed class TestStartup { + private SimpleInternalResource _testInternalResource; public IServiceProvider ConfigureServices(IServiceCollection services) { + _testInternalResource = new SimpleInternalResource { IsUp = true }; services.AddMonitoring(Assembly.GetEntryAssembly(), options => { options.AddShutdownMonitor(configOverride:new ResourceMonitorConfiguration(true, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100))); + options.AddInternalResourceMonitor(_testInternalResource, "simple resource", configOverride: new ResourceMonitorConfiguration(true, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100)), isCritical: true); }); return services.BuildServiceProvider(); } @@ -21,6 +26,15 @@ public IServiceProvider ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMonitoringEndpoint(options => options.Path = "/health"); + + app.Run(async context => + { + if (context.Request.Query.ContainsKey("SetToUp")) + { _testInternalResource.IsUp = true;} + + if (context.Request.Query.ContainsKey("SetToDown")) + { _testInternalResource.IsUp = false; } + }); } } }