diff --git a/devdocs/todo.txt b/devdocs/todo.txt index b3dbb7ab..52aad238 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -8,7 +8,8 @@ working on: BACKUP finish routes, on demand and download and listing Then test with actual attachments to ensure it's working for that part as well - + Upload backup file so can restore? + only works if we have automated restore which is doubtful at any point todo: OPS routes (SERVER AND CLIENT) - Backup, restore https://rockfish.ayanova.com/default.htm#!/rfcaseEdit/3369 diff --git a/server/AyaNova/Controllers/BackupController.cs b/server/AyaNova/Controllers/BackupController.cs index 44a60e1c..fd6b8b5c 100644 --- a/server/AyaNova/Controllers/BackupController.cs +++ b/server/AyaNova/Controllers/BackupController.cs @@ -1,10 +1,14 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; using AyaNova.Models; using AyaNova.Api.ControllerHelpers; - +using AyaNova.Biz; +using System.Threading.Tasks; +using AyaNova.Biz; +using Newtonsoft.Json.Linq; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //************************************************************************************************************** */ @@ -41,7 +45,7 @@ namespace AyaNova.Api.Controllers private readonly AyContext ct; private readonly ILogger log; private readonly ApiServerState serverState; - + /// @@ -57,21 +61,61 @@ namespace AyaNova.Api.Controllers serverState = apiServerState; } -/* -LOOKAT: -A backup archive consists of a similar format to the v7 data dumper utility, json file per object in subdirectories corresponding to object type all in a zip archive -Route to trigger restore from selected file -Route to force immediate backup -Backup code in biz objects "IBackup" interface (which in turn is probably going to implement an IExport interface as it serves dual purpose -of exporting data, or maybe that's special purpose custom objects for exporting like csv etc since there is likely a graph of data involved) - - object is exported / backed up to json -Restore code in biz objects "IRestore" interface - - object(s) imported via restore and data given to them or file or whatever (See discource project) + /// + /// Trigger immediate system backup + /// + /// + /// Job Id + [HttpPost("backup-now")] + [Authorize] + public async Task PostServerState() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - */ + if (!Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)) + return StatusCode(403, new ApiNotAuthorizedResponse()); -//TODO: Copy the code from ImportAyaNova7Controller upload method instead of this old crap + + var JobName = $"Backup (on demand)"; + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.ObjectType = AyaType.NoType; + j.JobType = JobType.Backup; + j.SubType = JobSubType.NotSet; + j.Exclusive = true; + + await JobsBiz.AddJobAsync(j, ct); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.ServerJob, AyaEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + + + + + + + + + #region old shit + /* + LOOKAT: + A backup archive consists of a similar format to the v7 data dumper utility, json file per object in subdirectories corresponding to object type all in a zip archive + + Route to trigger restore from selected file + Route to force immediate backup + Backup code in biz objects "IBackup" interface (which in turn is probably going to implement an IExport interface as it serves dual purpose + of exporting data, or maybe that's special purpose custom objects for exporting like csv etc since there is likely a graph of data involved) + - object is exported / backed up to json + Restore code in biz objects "IRestore" interface + - object(s) imported via restore and data given to them or file or whatever (See discource project) + + */ + + //TODO: Copy the code from ImportAyaNova7Controller upload method instead of this old crap // /// // /// Upload AyaNova backup files @@ -85,7 +129,7 @@ Restore code in biz objects "IRestore" interface // public async Task Upload() // { // //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming - + // try // { // if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) @@ -158,7 +202,7 @@ Restore code in biz objects "IRestore" interface // section = await reader.ReadNextSectionAsync(); // } - + // } @@ -183,7 +227,7 @@ Restore code in biz objects "IRestore" interface // } // return mediaType.Encoding; // } - + #endregion old shit }//eoc }//eons diff --git a/server/AyaNova/biz/JobsBiz.cs b/server/AyaNova/biz/JobsBiz.cs index 3e81836a..5b19149c 100644 --- a/server/AyaNova/biz/JobsBiz.cs +++ b/server/AyaNova/biz/JobsBiz.cs @@ -407,6 +407,10 @@ namespace AyaNova.Biz switch (job.JobType) { + case JobType.Backup: + //This is called when on demand only, normal backups are processed above with normal system jobs + await CoreJobBackup.DoWorkAsync(ct, true); + break; case JobType.TestWidgetJob: o = (IJobObject)BizObjectFactory.GetBizObject(AyaType.Widget, ct); break; @@ -422,7 +426,8 @@ namespace AyaNova.Biz throw new System.NotSupportedException($"ProcessJobAsync type {job.JobType.ToString()} is not supported"); } - await o.HandleJobAsync(job); + if (o != null) + await o.HandleJobAsync(job); log.LogDebug($"ProcessJobAsync -> Job completed {JobDescription}"); } diff --git a/server/AyaNova/generator/CoreJobBackup.cs b/server/AyaNova/generator/CoreJobBackup.cs index ab707416..c9676e41 100644 --- a/server/AyaNova/generator/CoreJobBackup.cs +++ b/server/AyaNova/generator/CoreJobBackup.cs @@ -22,45 +22,47 @@ namespace AyaNova.Biz //////////////////////////////////////////////////////////////////////////////////////////////// // // - public static async Task DoWorkAsync(AyContext ct) + public static async Task DoWorkAsync(AyContext ct, bool OnDemand = false) { if (BackupIsRunning) return; - - log.LogTrace("Checking if backup should run"); //get NOW in utc DateTime utcNow = DateTime.UtcNow; - //what time should we backup today? - DateTime todayBackupTime = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, ServerGlobalOpsSettings.BackupTime.Hour, ServerGlobalOpsSettings.BackupTime.Minute, 0, DateTimeKind.Utc);//first start with NOW - //Are we there yet? - if (utcNow < todayBackupTime) + if (!OnDemand) { - log.LogTrace("Not past backup time yet"); return;//nope + log.LogTrace("Checking if backup should run"); + + //what time should we backup today? + DateTime todayBackupTime = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, ServerGlobalOpsSettings.BackupTime.Hour, ServerGlobalOpsSettings.BackupTime.Minute, 0, DateTimeKind.Utc);//first start with NOW + //Are we there yet? + if (utcNow < todayBackupTime) + { + log.LogTrace("Not past backup time yet"); return;//nope + } + //Yes, we've passed into the backup window time, but that's also true if we just ran the backup as well so + //need to check for that as well... + //Has last backup run more than 24 hours ago? + if (ServerGlobalOpsSettings.LastBackup > utcNow.AddHours(-24)) + { + log.LogTrace("Hasn't been 24 hours since last backup yet"); return;//nope//nope, so we have already run today's backup + } + //Ok, we're into backup time and it's been more than 24 hours since it last ran so let's do this... } - //Yes, we've passed into the backup window time, but that's also true if we just ran the backup as well so - //need to check for that as well... - //Has last backup run more than 24 hours ago? - if (ServerGlobalOpsSettings.LastBackup > utcNow.AddHours(-24)) - { - log.LogTrace("Hasn't been 24 hours since last backup yet"); return;//nope//nope, so we have already run today's backup - } - //Ok, we're into backup time and it's been more than 24 hours since it last ran so let's do this... AyaNova.Api.ControllerHelpers.ApiServerState apiServerState = null; try { BackupIsRunning = true; //LOCK DOWN SERVER apiServerState = (AyaNova.Api.ControllerHelpers.ApiServerState)ServiceProviderProvider.Provider.GetService(typeof(AyaNova.Api.ControllerHelpers.ApiServerState)); - apiServerState.SetClosed("BACKUP JOB RUNNING"); + apiServerState.SetClosed("BACKUP RUNNING"); log.LogDebug("Backup starting"); //************* - //DO DATA BACKUP //build command - //this is valid: + //this is valid on windows //C:\data\code\PostgreSQLPortable_12.0\App\PgSQL\bin\pg_dump --dbname=postgresql://postgres:raven@127.0.0.1:5432/AyaNova -Fc > huge_new.backup - //"AYANOVA_DB_CONNECTION": "Server=localhost;Username=postgres;Password=raven;Database=AyaNova;", + Npgsql.NpgsqlConnectionStringBuilder PostgresConnectionString = new Npgsql.NpgsqlConnectionStringBuilder(ServerBootConfig.AYANOVA_DB_CONNECTION); var DBNameParameter = $"--dbname=postgresql://{PostgresConnectionString.Username}:{PostgresConnectionString.Password}@{PostgresConnectionString.Host}:{PostgresConnectionString.Port}/{PostgresConnectionString.Database}"; @@ -80,6 +82,7 @@ namespace AyaNova.Biz else log.LogError($"BACKUP ERROR: {Result}"); + //PRUNE DATA BACKUP SETS NOT KEPT FileUtil.DatabaseBackupCleanUp(ServerGlobalOpsSettings.BackupSetsToKeep); @@ -96,13 +99,14 @@ namespace AyaNova.Biz //v.next - COPY TO ONLINE STORAGE //*************** - - //Update last backup - var biz = GlobalOpsSettingsBiz.GetBiz(ct); - var OpSet = await biz.GetAsync(false); - OpSet.LastBackup = utcNow; - await biz.ReplaceAsync(OpSet); - + if (!OnDemand) + { + //Update last backup + var biz = GlobalOpsSettingsBiz.GetBiz(ct); + var OpSet = await biz.GetAsync(false); + OpSet.LastBackup = utcNow; + await biz.ReplaceAsync(OpSet); + } log.LogDebug("Backup completed"); } catch (Exception ex) @@ -116,6 +120,9 @@ namespace AyaNova.Biz BackupIsRunning = false; } } + + + ///////////////////////////////////////////////////////////////////// }//eoc }//eons