This commit is contained in:
2020-06-15 14:48:45 +00:00
parent f80d1bbca0
commit e7b5e1a388
5 changed files with 77 additions and 153 deletions

View File

@@ -72,20 +72,6 @@ namespace AyaNova.Api.Controllers
// /// <summary>
// /// Get Trial status of license
// /// </summary>
// /// <returns>True if a trial license</returns>
// [AllowAnonymous]
// [HttpGet("trial")]
// public ActionResult GetTrialFlag()
// {
// //note: this route is called by the client as the first action so it also acts like a ping to see if the server is up as well
// if (serverState.IsClosed)
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
// return Ok(ApiOkResponse.Response(AyaNova.Core.License.ActiveKey.TrialLicense));
// }
/// <summary> /// <summary>
@@ -121,9 +107,12 @@ namespace AyaNova.Api.Controllers
//main thing is to not log anything but OK response //main thing is to not log anything but OK response
if (ret == "ok") if (ret == "ok")
{ {
//Log //Eventlog
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.License, AyaEvent.LicenseFetch), ct); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.License, AyaEvent.LicenseFetch), ct);
}
else
{
log.LogError($"LicenseController::FetchLicense - failed: {ret}");
} }
//any response here is OK, there's not necessarily an error, just no key or some mundane problem not technical but biz so let //any response here is OK, there's not necessarily an error, just no key or some mundane problem not technical but biz so let

View File

@@ -570,7 +570,7 @@ namespace AyaNova
if (ServerBootConfig.AYANOVA_SERVER_TEST_MODE) if (ServerBootConfig.AYANOVA_SERVER_TEST_MODE)
{ {
_newLog.LogInformation($"Server test mode seeding, level is {ServerBootConfig.AYANOVA_SERVER_TEST_MODE_SEEDLEVEL}, tz offset is {ServerBootConfig.AYANOVA_SERVER_TEST_MODE_TZ_OFFSET}"); _newLog.LogInformation($"Server test mode seeding, level is {ServerBootConfig.AYANOVA_SERVER_TEST_MODE_SEEDLEVEL}, tz offset is {ServerBootConfig.AYANOVA_SERVER_TEST_MODE_TZ_OFFSET}");
AyaNova.Core.License.FetchKeyAsync(apiServerState, dbContext, _newLog).Wait(); AyaNova.Core.License.FetchKeyAsync(apiServerState, dbContext, _newLog, true).Wait();
//NOTE: For unit testing make sure the time zone is same as tester to ensure list filter by date tests will work because server is on same page as user in terms of time //NOTE: For unit testing make sure the time zone is same as tester to ensure list filter by date tests will work because server is on same page as user in terms of time
var seed = new Util.Seeder(); var seed = new Util.Seeder();
seed.SeedDatabaseAsync(Seeder.Level.StringToSeedLevel(ServerBootConfig.AYANOVA_SERVER_TEST_MODE_SEEDLEVEL), ServerBootConfig.AYANOVA_SERVER_TEST_MODE_TZ_OFFSET).Wait(); seed.SeedDatabaseAsync(Seeder.Level.StringToSeedLevel(ServerBootConfig.AYANOVA_SERVER_TEST_MODE_SEEDLEVEL), ServerBootConfig.AYANOVA_SERVER_TEST_MODE_TZ_OFFSET).Wait();

View File

@@ -155,9 +155,9 @@ namespace AyaNova.Biz
ActivelyProcessing = true; ActivelyProcessing = true;
try try
{ {
//Sweep jobs table //## Critical internal jobs
//System.Diagnostics.Debug.WriteLine($"JobsBiz processing sweeper"); await CoreJobSweeper.DoSweepAsync();
await CoreJobSweeper.DoSweepAsync();//run exclusively await CoreJobLicense.DoWorkAsync();
//BIZOBJECT DYNAMIC JOBS //BIZOBJECT DYNAMIC JOBS
//get a list of exclusive jobs that are due to happen //get a list of exclusive jobs that are due to happen

View File

@@ -4,6 +4,8 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using AyaNova.Models; using AyaNova.Models;
using AyaNova.Core;
using AyaNova.Util;
namespace AyaNova.Biz namespace AyaNova.Biz
{ {
@@ -16,141 +18,74 @@ namespace AyaNova.Biz
internal static class CoreJobLicense internal static class CoreJobLicense
{ {
private static ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("CoreJobLicense"); private static ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("CoreJobLicense");
private static DateTime lastCheck = DateTime.MinValue; private static DateTime _lastCheck = DateTime.MinValue;
//define timespans for various existing license scenarios private static TimeSpan FAST_TRACK = new TimeSpan(0, 0, 30, 0);
//120 days after a new license purchase it should check for revoke from chargebacks private static TimeSpan SLOW_TRACK = new TimeSpan(24, 0, 0);
//Trial should check regularly for purchase
//revoked should do what?
//rental licenses need to check more often than perpetual
//slow and fast scenarios maybe? Just two timespans, fast for likely purchaser and slow for ongoing rental maint etc
private static TimeSpan SWEEP_EVERY_INTERVAL = new TimeSpan(0, 30, 0);
private static TimeSpan SUCCEEDED_JOBS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(14, 0, 0, 0);//14 days
private static TimeSpan FAILED_JOBS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(14, 0, 0, 0);//14 days (gives people time to notice and look into it)
private static TimeSpan INTERNAL_JOBS_LOGS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(14, 0, 0, 0);//14 days
private static TimeSpan RUNNING_JOBS_BECOME_FAILED_AFTER_THIS_TIMESPAN = new TimeSpan(24, 0, 0);//24 hours (time running jobs are allowed to sit in "running" state before considered failed)
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
// DoSweep // DoWork
// //
public static async Task DoSweepAsync() public static async Task DoWorkAsync()
{ {
//This will get triggered roughly every minute, but we don't want to sweep that frequently log.LogTrace("Job starting");
if (DateTime.UtcNow - lastCheck < SWEEP_EVERY_INTERVAL) var tsSinceLastCheck = DateTime.UtcNow - _lastCheck;
//which track are we on?
/*
NONE = 0,//fast track
ActiveTrial = 1,//slow track
ExpiredTrial = 2,//fast track
ActivePurchased = 3,//slow track
ExpiredPurchased = 4,//fast track
Revoked = 5//slow track
*/
TimeSpan tsCheckFrequency;
switch (AyaNova.Core.License.ActiveKey.Status)
{
case AyaNova.Core.License.AyaNovaLicenseKey.LicenseStatus.NONE:
case AyaNova.Core.License.AyaNovaLicenseKey.LicenseStatus.ExpiredTrial:
case AyaNova.Core.License.AyaNovaLicenseKey.LicenseStatus.ExpiredPurchased:
tsCheckFrequency = FAST_TRACK;
break;
default:
tsCheckFrequency = SLOW_TRACK;
break;
}
log.LogTrace($"Check frequency{tsCheckFrequency}");
if (tsSinceLastCheck < tsCheckFrequency)
{
log.LogTrace($"Not yet");
return; return;
}
log.LogTrace("Sweep starting");
using (AyContext ct = AyaNova.Util.ServiceProviderProvider.DBContext) using (AyContext ct = AyaNova.Util.ServiceProviderProvider.DBContext)
{ {
//SWEEP SUCCESSFUL JOBS try//allow exception to bubble up but using try block to ensure the lastcheck is set properly regardless so this doesn't spin out of control
//calculate cutoff to delete
DateTime dtDeleteCutoff = DateTime.UtcNow - SUCCEEDED_JOBS_DELETE_AFTER_THIS_TIMESPAN;
await sweepAsync(ct, dtDeleteCutoff, JobStatus.Completed);
//SWEEP FAILED JOBS
//calculate cutoff to delete
dtDeleteCutoff = DateTime.UtcNow - FAILED_JOBS_DELETE_AFTER_THIS_TIMESPAN;
await sweepAsync(ct, dtDeleteCutoff, JobStatus.Failed);
//KILL STUCK JOBS
//calculate cutoff to delete
DateTime dtRunningDeadline = DateTime.UtcNow - RUNNING_JOBS_BECOME_FAILED_AFTER_THIS_TIMESPAN;
await killStuckJobsAsync(ct, dtRunningDeadline);
//SWEEP INTERNAL JOB LOG
//calculate cutoff to delete
dtDeleteCutoff = DateTime.UtcNow - INTERNAL_JOBS_LOGS_DELETE_AFTER_THIS_TIMESPAN;
await SweepInternalJobsLogsAsync(ct, dtDeleteCutoff);
}
lastCheck = DateTime.UtcNow;
}
private static async Task sweepAsync(AyContext ct, DateTime dtDeleteCutoff, JobStatus jobStatus)
{
//Get the deleteable succeeded jobs list
var jobs = await ct.OpsJob
.AsNoTracking()
.Where(z => z.Created < dtDeleteCutoff && z.JobStatus == jobStatus)
.OrderBy(z => z.Created)
.ToListAsync();
log.LogTrace($"SweepAsync processing: cutoff={dtDeleteCutoff.ToString()}, for {jobs.Count.ToString()} jobs of status {jobStatus.ToString()}");
foreach (OpsJob j in jobs)
{
try
{ {
var ServerState = (AyaNova.Api.ControllerHelpers.ApiServerState)ServiceProviderProvider.Provider.GetService(typeof(AyaNova.Api.ControllerHelpers.ApiServerState));
await JobsBiz.RemoveJobAndLogsAsync(j.GId); var ret = await AyaNova.Core.License.FetchKeyAsync(ServerState, ct, log, true);
//most often the result will be "notfound" but in future might be other results
if (ret == "ok")
{
//Eventlog
await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, 0, AyaType.License, AyaEvent.LicenseFetch, "FromCoreJob"), ct);
}
if(ret!="notfound")
{
//I'm thinking do not log internally failed except as trace event as this would fill up log file
//instead if they have a license issue they can do manual fetch and then that will log so they can see error
//(also it will return an error to the client)
log.LogTrace($"FetchLicense - failed: {ret}");
}
} }
catch (Exception ex) finally
{ {
log.LogError(ex, "sweepAsync exception calling JobsBiz.RemoveJobAndLogsAsync"); _lastCheck = DateTime.UtcNow;
//for now just throw it but this needs to be removed when logging added and better handling
throw (ex);
} }
} }
} }
/// <summary>
/// Kill jobs that have been stuck in "running" state for too long
/// </summary>
private static async Task killStuckJobsAsync(AyContext ct, DateTime dtRunningDeadline)
{
//Get the deleteable succeeded jobs list
var jobs = await ct.OpsJob
.AsNoTracking()
.Where(z => z.Created < dtRunningDeadline && z.JobStatus == JobStatus.Running)
.OrderBy(z => z.Created)
.ToListAsync();
log.LogTrace($"killStuckJobsAsync processing: cutoff={dtRunningDeadline.ToString()}, for {jobs.Count.ToString()} jobs of status {JobStatus.Running.ToString()}");
foreach (OpsJob j in jobs)
{
//OPSMETRIC
await JobsBiz.LogJobAsync(j.GId, "Job took too long to run - setting to failed");
log.LogError($"Job found job stuck in running status and set to failed: deadline={dtRunningDeadline.ToString()}, jobId={j.GId.ToString()}, jobname={j.Name}, jobtype={j.JobType.ToString()}, jobObjectType={j.ObjectType.ToString()}, jobObjectId={j.ObjectId.ToString()}");
await JobsBiz.UpdateJobStatusAsync(j.GId, JobStatus.Failed);
}
}
private static async Task SweepInternalJobsLogsAsync(AyContext ct, DateTime dtDeleteCutoff)
{
//Get the deleteable list (this is for reporting, could easily just do it in one go)
var logs = await ct.OpsJobLog
.AsNoTracking()
.Where(z => z.Created < dtDeleteCutoff)
.OrderBy(z => z.Created)
.ToListAsync();
log.LogTrace($"SweepInternalJobsLogsAsync processing: cutoff={dtDeleteCutoff.ToString()}, for {logs.Count.ToString()} log entries");
foreach (OpsJobLog l in logs)
{
try
{
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from aopsjoblog where gid = {l.GId}");
}
catch (Exception ex)
{
log.LogError(ex, "SweepInternalJobsLogsAsync exception removed old log entries");
throw (ex);
}
}
}
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
}//eoc }//eoc
}//eons }//eons

View File

@@ -86,12 +86,12 @@ namespace AyaNova.Core
public enum LicenseStatus public enum LicenseStatus
{ {
NONE = 0, NONE = 0,//fast track
ActiveTrial = 1, ActiveTrial = 1,//slow track
ExpiredTrial = 2, ExpiredTrial = 2,//fast track
ActivePurchased = 3, ActivePurchased = 3,//slow track
ExpiredPurchased = 4, ExpiredPurchased = 4,//fast track
Revoked = 5 Revoked = 5//slow track
} }
public AyaNovaLicenseKey() public AyaNovaLicenseKey()
@@ -174,7 +174,6 @@ namespace AyaNova.Core
return LicenseStatus.ActivePurchased; return LicenseStatus.ActivePurchased;
if (!TrialLicense && LicenseExpired) if (!TrialLicense && LicenseExpired)
return LicenseStatus.ExpiredPurchased; return LicenseStatus.ExpiredPurchased;
throw new System.Exception("License::Status - unable to determine license status"); throw new System.Exception("License::Status - unable to determine license status");
} }
} }
@@ -484,9 +483,13 @@ namespace AyaNova.Core
/// Fetch a key, validate it and install it in the db then initialize with it /// Fetch a key, validate it and install it in the db then initialize with it
/// </summary> /// </summary>
/// <returns>Result string</returns> /// <returns>Result string</returns>
internal static async Task<string> FetchKeyAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log, bool calledFromInternalJob = true) internal static async Task<string> FetchKeyAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log, bool calledFromInternalJob)
{ {
log.LogInformation($"Fetching license for DBID {LicenseDbId.ToString()}"); if (calledFromInternalJob)
log.LogTrace($"Fetching license for DBID {LicenseDbId.ToString()} (called by job)");
else
log.LogInformation($"Fetching license for DBID {LicenseDbId.ToString()}");
string sUrl = $"{LICENSE_SERVER_URL}rvf/{LicenseDbId.ToString()}"; string sUrl = $"{LICENSE_SERVER_URL}rvf/{LicenseDbId.ToString()}";
try try
{ {
@@ -504,11 +507,8 @@ namespace AyaNova.Core
if (ParsedKey != null) if (ParsedKey != null)
{ {
await InstallAsync(keyText, ParsedKey, apiServerState, ct, log); await InstallAsync(keyText, ParsedKey, apiServerState, ct, log);
log.LogInformation($"Installed new license key {ParsedKey.Id}");
//Log the license info so it's on the record //Log the license info so it's on the record
log.LogInformation($"License - new key installed [{AyaNova.Core.License.LicenseInfoLogFormat}]"); log.LogInformation($"License - new key installed [{AyaNova.Core.License.LicenseInfoLogFormat}]");
return "ok"; return "ok";
} }
return $"E1020 - Error fetching license key: No key was returned"; return $"E1020 - Error fetching license key: No key was returned";