Added healthchecks
Configured scheduler schema Added dataprotection support
This commit is contained in:
@@ -58,6 +58,8 @@ bld/
|
|||||||
# Visual Studio 2015/2017 cache/options directory
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
bin/
|
||||||
|
certificates/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
#wwwroot/
|
#wwwroot/
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using PostFundManagement.Infrastructure.Database;
|
using PostFundManagement.Infrastructure.Database;
|
||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
namespace PostFundManagement.Infrastructure.Extensions;
|
namespace PostFundManagement.Api.Extensions;
|
||||||
|
|
||||||
public static class Postgres
|
public static class Postgres
|
||||||
{
|
{
|
||||||
+3
-1
@@ -6,7 +6,7 @@ using PostFundManagement.Infrastructure.Database;
|
|||||||
|
|
||||||
namespace PostFundManagement.Api.Extensions;
|
namespace PostFundManagement.Api.Extensions;
|
||||||
|
|
||||||
public static class Api
|
public static class Security
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddSecurityApiSdk(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddSecurityApiSdk(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
@@ -42,6 +42,8 @@ public static class Api
|
|||||||
|
|
||||||
public static IServiceCollection AddWebSecurity(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddWebSecurity(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
services.AddAuthorization();
|
||||||
|
|
||||||
var certString = configuration["DataProtection:Certificate"] ?? configuration["DataProtection__Certificate"];
|
var certString = configuration["DataProtection:Certificate"] ?? configuration["DataProtection__Certificate"];
|
||||||
var certPassword = configuration["DataProtection:Password"] ?? configuration["DataProtection__Password"];
|
var certPassword = configuration["DataProtection:Password"] ?? configuration["DataProtection__Password"];
|
||||||
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
using PostFundManagement.Api.Extensions;
|
using PostFundManagement.Api.Extensions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Health;
|
||||||
using PostFundManagement.Domain.Mediator;
|
using PostFundManagement.Domain.Mediator;
|
||||||
using PostFundManagement.Infrastructure.Extensions;
|
|
||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -21,7 +22,7 @@ builder.Services.AddWebSecurity(builder.Configuration);
|
|||||||
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>));
|
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>));
|
||||||
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>));
|
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>));
|
||||||
|
|
||||||
builder.Services.AddQuartzSchedulerClient(DefaultSchedulerName, builder.Configuration);
|
builder.Services.AddQuartzScheduler(DefaultSchedulerName, builder.Configuration);
|
||||||
|
|
||||||
builder.Services.AddEmailServices(builder.Configuration);
|
builder.Services.AddEmailServices(builder.Configuration);
|
||||||
|
|
||||||
@@ -30,10 +31,10 @@ builder.Services.AddHashServices(builder.Configuration);
|
|||||||
builder.Services.AddDataProtectionDatabase(builder.Configuration);
|
builder.Services.AddDataProtectionDatabase(builder.Configuration);
|
||||||
builder.Services.AddApplicationDbContext(builder.Configuration);
|
builder.Services.AddApplicationDbContext(builder.Configuration);
|
||||||
|
|
||||||
// TODO: add healthcheck services
|
builder.Services.AddHealthChecks()
|
||||||
// builder.Services.AddMidrandShopPostgresHealthCheck();
|
.AddCheck<QuartzHealthCheck>("QuartzScheduler")
|
||||||
// builder.Services.AddMidrandShopQuartzHealthCheck();
|
.AddCheck<PostgresHealthCheck>("PostgresDatabase")
|
||||||
// builder.Services.AddHealthChecksSupport(builder.Configuration);
|
.AddCheck("Self", () => HealthCheckResult.Healthy());
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,12 @@ public static class Quartz
|
|||||||
|
|
||||||
storage.UseSystemTextJsonSerializer();
|
storage.UseSystemTextJsonSerializer();
|
||||||
storage.SetProperty("quartz.jobStore.clustered", "true");
|
storage.SetProperty("quartz.jobStore.clustered", "true");
|
||||||
storage.SetProperty("quartz.jobStore.tablePrefix", "qrtz_");
|
|
||||||
|
|
||||||
storage.UsePostgres(connectionString!);
|
storage.UsePostgres(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = connectionString!;
|
||||||
|
options.TablePrefix = "quartz.qrtz_";
|
||||||
|
});
|
||||||
storage.UseClustering(cluster =>
|
storage.UseClustering(cluster =>
|
||||||
{
|
{
|
||||||
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
||||||
@@ -69,9 +72,12 @@ public static class Quartz
|
|||||||
|
|
||||||
storage.UseSystemTextJsonSerializer();
|
storage.UseSystemTextJsonSerializer();
|
||||||
storage.SetProperty("quartz.jobStore.clustered", "true");
|
storage.SetProperty("quartz.jobStore.clustered", "true");
|
||||||
storage.SetProperty("quartz.jobStore.tablePrefix", "qrtz_");
|
|
||||||
|
|
||||||
storage.UsePostgres(connectionString!);
|
storage.UsePostgres(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = connectionString!;
|
||||||
|
options.TablePrefix = "quartz.qrtz_";
|
||||||
|
});
|
||||||
storage.UseClustering(cluster =>
|
storage.UseClustering(cluster =>
|
||||||
{
|
{
|
||||||
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Domain.Health;
|
||||||
|
|
||||||
|
public sealed class PostgresHealthCheck(IConfiguration configuration) : IHealthCheck
|
||||||
|
{
|
||||||
|
private readonly string connectionString = configuration.GetConnectionString(DatabaseConfigName)!;
|
||||||
|
|
||||||
|
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var dataSource = NpgsqlDataSource.Create(connectionString);
|
||||||
|
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
|
||||||
|
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT 1";
|
||||||
|
|
||||||
|
await command.ExecuteScalarAsync(cancellationToken);
|
||||||
|
|
||||||
|
return HealthCheckResult.Healthy($"{DatabaseConfigName} is responsive.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return HealthCheckResult.Unhealthy($"{DatabaseConfigName} is unreachable.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Domain.Health;
|
||||||
|
|
||||||
|
public sealed class QuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
|
||||||
|
{
|
||||||
|
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var scheduler = await schedulerFactory.GetScheduler(DefaultSchedulerName, cancellationToken);
|
||||||
|
|
||||||
|
if(scheduler == null)
|
||||||
|
return HealthCheckResult.Unhealthy($"Scheduler with name '{DefaultSchedulerName}' not found.");
|
||||||
|
|
||||||
|
if (!scheduler.IsStarted)
|
||||||
|
return HealthCheckResult.Unhealthy($"{DefaultSchedulerName} Quartz scheduler is not running");
|
||||||
|
|
||||||
|
await scheduler.CheckExists(new JobKey(Guid.NewGuid().ToString()), cancellationToken);
|
||||||
|
|
||||||
|
return HealthCheckResult.Healthy($"{DefaultSchedulerName} Quartz scheduler is ready");
|
||||||
|
}
|
||||||
|
catch (SchedulerException)
|
||||||
|
{
|
||||||
|
return HealthCheckResult.Unhealthy($"{DefaultSchedulerName} Quartz scheduler cannot connect to the store");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
-- This script is for PostgreSQL
|
||||||
|
|
||||||
|
-- This initializes the database to pristine for Quartz, by first removing any existing Quartz tables
|
||||||
|
-- and then recreating them from scratch.
|
||||||
|
-- Should you only require it to create the tables, set DropDb to 0.
|
||||||
|
-- 1. Create the dedicated schema
|
||||||
|
CREATE SCHEMA IF NOT EXISTS quartz;
|
||||||
|
|
||||||
|
-- 2. Set the search path so the creation script targets this schema
|
||||||
|
SET search_path TO quartz;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE DropDb INT := 1; -- Set this to 0 to skip DROP statements, 1 to include them
|
||||||
|
BEGIN
|
||||||
|
IF DropDb = 1 THEN
|
||||||
|
SET client_min_messages = WARNING;
|
||||||
|
DROP TABLE IF EXISTS qrtz_fired_triggers;
|
||||||
|
DROP TABLE IF EXISTS qrtz_paused_trigger_grps;
|
||||||
|
DROP TABLE IF EXISTS qrtz_scheduler_state;
|
||||||
|
DROP TABLE IF EXISTS qrtz_locks;
|
||||||
|
DROP TABLE IF EXISTS qrtz_simprop_triggers;
|
||||||
|
DROP TABLE IF EXISTS qrtz_simple_triggers;
|
||||||
|
DROP TABLE IF EXISTS qrtz_cron_triggers;
|
||||||
|
DROP TABLE IF EXISTS qrtz_blob_triggers;
|
||||||
|
DROP TABLE IF EXISTS qrtz_triggers;
|
||||||
|
DROP TABLE IF EXISTS qrtz_job_details;
|
||||||
|
DROP TABLE IF EXISTS qrtz_calendars;
|
||||||
|
SET client_min_messages = NOTICE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_job_details
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
job_name TEXT NOT NULL,
|
||||||
|
job_group TEXT NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
job_class_name TEXT NOT NULL,
|
||||||
|
is_durable BOOL NOT NULL,
|
||||||
|
is_nonconcurrent BOOL NOT NULL,
|
||||||
|
is_update_data BOOL NOT NULL,
|
||||||
|
requests_recovery BOOL NOT NULL,
|
||||||
|
job_data BYTEA NULL,
|
||||||
|
PRIMARY KEY (sched_name, job_name, job_group)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_triggers
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
trigger_name TEXT NOT NULL,
|
||||||
|
trigger_group TEXT NOT NULL,
|
||||||
|
job_name TEXT NOT NULL,
|
||||||
|
job_group TEXT NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
next_fire_time BIGINT NULL,
|
||||||
|
prev_fire_time BIGINT NULL,
|
||||||
|
priority INTEGER NULL,
|
||||||
|
trigger_state TEXT NOT NULL,
|
||||||
|
trigger_type TEXT NOT NULL,
|
||||||
|
start_time BIGINT NOT NULL,
|
||||||
|
end_time BIGINT NULL,
|
||||||
|
calendar_name TEXT NULL,
|
||||||
|
misfire_instr SMALLINT NULL,
|
||||||
|
misfire_orig_fire_time BIGINT NULL,
|
||||||
|
execution_group VARCHAR(200) NULL,
|
||||||
|
preferred_node VARCHAR(200) NULL,
|
||||||
|
preferred_node_auto BOOL NOT NULL DEFAULT FALSE,
|
||||||
|
job_data BYTEA NULL,
|
||||||
|
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||||
|
FOREIGN KEY (sched_name, job_name, job_group)
|
||||||
|
REFERENCES qrtz_job_details (sched_name, job_name, job_group)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_simple_triggers
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
trigger_name TEXT NOT NULL,
|
||||||
|
trigger_group TEXT NOT NULL,
|
||||||
|
repeat_count BIGINT NOT NULL,
|
||||||
|
repeat_interval BIGINT NOT NULL,
|
||||||
|
times_triggered BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||||
|
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||||
|
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_simprop_triggers
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
trigger_name TEXT NOT NULL,
|
||||||
|
trigger_group TEXT NOT NULL,
|
||||||
|
str_prop_1 TEXT NULL,
|
||||||
|
str_prop_2 TEXT NULL,
|
||||||
|
str_prop_3 TEXT NULL,
|
||||||
|
int_prop_1 INTEGER NULL,
|
||||||
|
int_prop_2 INTEGER NULL,
|
||||||
|
long_prop_1 BIGINT NULL,
|
||||||
|
long_prop_2 BIGINT NULL,
|
||||||
|
dec_prop_1 NUMERIC NULL,
|
||||||
|
dec_prop_2 NUMERIC NULL,
|
||||||
|
bool_prop_1 BOOL NULL,
|
||||||
|
bool_prop_2 BOOL NULL,
|
||||||
|
time_zone_id TEXT NULL,
|
||||||
|
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||||
|
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||||
|
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_cron_triggers
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
trigger_name TEXT NOT NULL,
|
||||||
|
trigger_group TEXT NOT NULL,
|
||||||
|
cron_expression TEXT NOT NULL,
|
||||||
|
time_zone_id TEXT,
|
||||||
|
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||||
|
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||||
|
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_blob_triggers
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
trigger_name TEXT NOT NULL,
|
||||||
|
trigger_group TEXT NOT NULL,
|
||||||
|
blob_data BYTEA NULL,
|
||||||
|
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||||
|
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||||
|
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_calendars
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
calendar_name TEXT NOT NULL,
|
||||||
|
calendar BYTEA NOT NULL,
|
||||||
|
PRIMARY KEY (sched_name, calendar_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_paused_trigger_grps
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
trigger_group TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (sched_name, trigger_group)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_fired_triggers
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
entry_id TEXT NOT NULL,
|
||||||
|
trigger_name TEXT NOT NULL,
|
||||||
|
trigger_group TEXT NOT NULL,
|
||||||
|
instance_name TEXT NOT NULL,
|
||||||
|
fired_time BIGINT NOT NULL,
|
||||||
|
sched_time BIGINT NOT NULL,
|
||||||
|
priority INTEGER NOT NULL,
|
||||||
|
state TEXT NOT NULL,
|
||||||
|
job_name TEXT NULL,
|
||||||
|
job_group TEXT NULL,
|
||||||
|
is_nonconcurrent BOOL NOT NULL,
|
||||||
|
requests_recovery BOOL NULL,
|
||||||
|
execution_group VARCHAR(200) NULL,
|
||||||
|
PRIMARY KEY (sched_name, entry_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_scheduler_state
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
instance_name TEXT NOT NULL,
|
||||||
|
last_checkin_time BIGINT NOT NULL,
|
||||||
|
checkin_interval BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (sched_name, instance_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE qrtz_locks
|
||||||
|
(
|
||||||
|
sched_name TEXT NOT NULL,
|
||||||
|
lock_name TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (sched_name, lock_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_qrtz_j_g_n ON qrtz_job_details (sched_name, job_group, job_name);
|
||||||
|
CREATE INDEX idx_qrtz_t_j ON qrtz_triggers (sched_name, job_name, job_group);
|
||||||
|
CREATE INDEX idx_qrtz_t_c ON qrtz_triggers (sched_name, calendar_name);
|
||||||
|
CREATE INDEX idx_qrtz_t_g_n ON qrtz_triggers (sched_name, trigger_group, trigger_name);
|
||||||
|
CREATE INDEX idx_qrtz_t_nft_st ON qrtz_triggers (sched_name, trigger_state, next_fire_time);
|
||||||
|
CREATE INDEX idx_qrtz_ft_inst_job_req_rcvry ON qrtz_fired_triggers (sched_name, instance_name, requests_recovery);
|
||||||
|
CREATE INDEX idx_qrtz_ft_j_g ON qrtz_fired_triggers (sched_name, job_name, job_group);
|
||||||
|
CREATE INDEX idx_qrtz_ft_t_g ON qrtz_fired_triggers (sched_name, trigger_name, trigger_group);
|
||||||
Reference in New Issue
Block a user