diff --git a/SemanticBackup.Core/Models/BackupRecord.cs b/SemanticBackup.Core/Models/BackupRecord.cs index 40ea01d..7cd1ae2 100644 --- a/SemanticBackup.Core/Models/BackupRecord.cs +++ b/SemanticBackup.Core/Models/BackupRecord.cs @@ -10,7 +10,7 @@ public class BackupRecord [Required] public string BackupDatabaseInfoId { get; set; } public string Name { get; set; } - public string BackupStatus { get; set; } = BackupRecordBackupStatus.QUEUED.ToString(); + public string BackupStatus { get; set; } = BackupRecordStatus.QUEUED.ToString(); [Required] public string Path { get; set; } public DateTime StatusUpdateDateUTC { get; set; } = DateTime.UtcNow; @@ -24,7 +24,8 @@ public class BackupRecord public string RestoreExecutionMessage { get; set; } = string.Empty; public string RestoreConfirmationToken { get; set; } = string.Empty; } - public enum BackupRecordBackupStatus + + public enum BackupRecordStatus { QUEUED, EXECUTING, @@ -33,6 +34,7 @@ public enum BackupRecordBackupStatus READY, ERROR } + public enum BackupRecordExecutedDeliveryRunStatus { PENDING_EXECUTION, diff --git a/SemanticBackup.Core/Models/Requests/RSSettings.cs b/SemanticBackup.Core/Models/Requests/RSSettings.cs index cc5ab73..4027615 100644 --- a/SemanticBackup.Core/Models/Requests/RSSettings.cs +++ b/SemanticBackup.Core/Models/Requests/RSSettings.cs @@ -48,10 +48,22 @@ public class RSDropBoxSetting public string AccessToken { get; set; } public string Directory { get; set; } = "/"; } + public class RSAzureBlobStorageSetting { public bool IsEnabled { get; set; } = false; public string ConnectionString { get; set; } public string BlobContainer { get; set; } } + + public class RSObjectStorageSetting + { + public bool IsEnabled { get; set; } = false; + public string Server { get; set; } = "localhost"; + public int Port { get; set; } = 9000; + public string Bucket { get; set; } = "backups"; + public string AccessKey { get; set; } = string.Empty; + public string SecretKey { get; set; } = string.Empty; + public bool UseSsl { get; set; } = false; + } } diff --git a/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs b/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs index 2b15009..f62d923 100644 --- a/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs +++ b/SemanticBackup.Core/Models/Requests/ResourceGroupRequest.cs @@ -26,6 +26,7 @@ public class ResourceGroupRequest public RSEmailSMTPSetting RSEmailSMTPSetting { get; set; } = null; public RSDropBoxSetting RSDropBoxSetting { get; set; } = null; public RSAzureBlobStorageSetting RSAzureBlobStorageSetting { get; set; } = null; + public RSObjectStorageSetting RSObjectStorageSetting { get; set; } = null; public bool NotifyOnErrorBackups { get; set; } = false; public bool NotifyOnErrorBackupDelivery { get; set; } = false; public string NotifyEmailDestinations { get; set; } = null; diff --git a/SemanticBackup.Core/Models/ResourceGroup.cs b/SemanticBackup.Core/Models/ResourceGroup.cs index 091584e..219df48 100644 --- a/SemanticBackup.Core/Models/ResourceGroup.cs +++ b/SemanticBackup.Core/Models/ResourceGroup.cs @@ -30,6 +30,7 @@ public class ResourceGroup public bool NotifyOnErrorBackupDelivery { get; set; } = false; public string NotifyEmailDestinations { get; set; } = null; } + public class BackupDeliveryConfig { public DownloadLinkDeliveryConfig DownloadLink { get; set; } = new DownloadLinkDeliveryConfig(); @@ -37,12 +38,15 @@ public class BackupDeliveryConfig public SmtpDeliveryConfig Smtp { get; set; } = new SmtpDeliveryConfig(); public DropboxDeliveryConfig Dropbox { get; set; } = new DropboxDeliveryConfig(); public AzureBlobStorageDeliveryConfig AzureBlobStorage { get; set; } = new AzureBlobStorageDeliveryConfig(); + public ObjectStorageDeliveryConfig ObjectStorage { get; set; } = new ObjectStorageDeliveryConfig(); } + public class DownloadLinkDeliveryConfig { public bool IsEnabled { get; set; } = true; public string DownloadLinkType { get; set; } } + public class FtpDeliveryConfig { public bool IsEnabled { get; set; } = false; @@ -51,6 +55,7 @@ public class FtpDeliveryConfig public string Password { get; set; } public string Directory { get; set; } = "/"; } + public class SmtpDeliveryConfig { public bool IsEnabled { get; set; } = false; @@ -62,6 +67,7 @@ public class SmtpDeliveryConfig public string SMTPDefaultSMTPFromName { get; set; } public string SMTPDestinations { get; set; } } + public class DropboxDeliveryConfig { public bool IsEnabled { get; set; } = false; @@ -74,6 +80,18 @@ public class AzureBlobStorageDeliveryConfig public string ConnectionString { get; set; } public string BlobContainer { get; set; } } + + public class ObjectStorageDeliveryConfig + { + public bool IsEnabled { get; set; } = false; + public string Server { get; set; } = "localhost"; + public int Port { get; set; } = 9000; + public string Bucket { get; set; } = "buckups"; + public string AccessKey { get; set; } = string.Empty; + public string SecretKey { get; set; } = string.Empty; + public bool UseSsl { get; set; } = false; + } + //enums public enum DbTypes { @@ -81,6 +99,6 @@ public enum DbTypes } public enum BackupDeliveryConfigTypes { - DownloadLink, Ftp, Smtp, Dropbox, AzureBlobStorage + DownloadLink, Ftp, Smtp, Dropbox, AzureBlobStorage, ObjectStorage } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs index abae411..016ae3d 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundJob.cs @@ -22,10 +22,10 @@ public class BackupBackgroundJob : IHostedService public BackupBackgroundJob(ILogger logger, SystemConfigOptions persistanceOptions, IServiceScopeFactory serviceScopeFactory, BotsManagerBackgroundJob botsManagerBackgroundJob) { - this._logger = logger; - this._persistanceOptions = persistanceOptions; - this._serviceScopeFactory = serviceScopeFactory; - this._botsManagerBackgroundJob = botsManagerBackgroundJob; + _logger = logger; + _persistanceOptions = persistanceOptions; + _serviceScopeFactory = serviceScopeFactory; + _botsManagerBackgroundJob = botsManagerBackgroundJob; } public Task StartAsync(CancellationToken cancellationToken) @@ -48,67 +48,64 @@ private void SetupBackgroundService(CancellationToken cancellationToken) while (!cancellationToken.IsCancellationRequested) { //Await - await Task.Delay(5000); + await Task.Delay(5000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI Injections + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordStatus.QUEUED.ToString()); + if (queuedBackups != null && queuedBackups.Count > 0) { - //DI Injections - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordBackupStatus.QUEUED.ToString()); - if (queuedBackups != null && queuedBackups.Count > 0) + List scheduleToDelete = []; + foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) { - List scheduleToDelete = new List(); - foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) + _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}..."); + BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecord.BackupDatabaseInfoId); + if (backupDatabaseInfo == null) { - _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}..."); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - if (backupDatabaseInfo == null) + _logger.LogWarning($"No Database Info matches with Id: {backupRecord.BackupDatabaseInfoId}, Backup Database Record will be Deleted: {backupRecord.Id}"); + scheduleToDelete.Add(backupRecord.Id); + } + else + { + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); + if (resourceGroup == null) { - _logger.LogWarning($"No Database Info matches with Id: {backupRecord.BackupDatabaseInfoId}, Backup Database Record will be Deleted: {backupRecord.Id}"); + _logger.LogWarning($"The Database Id: {backupRecord.BackupDatabaseInfoId}, doesn't seem to have been assigned to a valid Resource Group Id: {backupDatabaseInfo.ResourceGroupId}, Record will be Deleted"); scheduleToDelete.Add(backupRecord.Id); } else { - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); - if (resourceGroup == null) - { - _logger.LogWarning($"The Database Id: {backupRecord.BackupDatabaseInfoId}, doesn't seem to have been assigned to a valid Resource Group Id: {backupDatabaseInfo.ResourceGroupId}, Record will be Deleted"); - scheduleToDelete.Add(backupRecord.Id); - } - else + if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) { - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - if (resourceGroup.DbType.Contains("SQLSERVER")) - _botsManagerBackgroundJob.AddBot(new SQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); - else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) - _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); - else - throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); - //Finally Update Status - bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordBackupStatus.EXECUTING.ToString()); - if (updated) - _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}...SUCCESS"); - else - _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{backupRecord.Id} status"); - } + if (resourceGroup.DbType.Contains("SQLSERVER")) + _botsManagerBackgroundJob.AddBot(new SQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); + else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) + _botsManagerBackgroundJob.AddBot(new MySQLBackupBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); + else + throw new Exception($"No Bot is registered to Handle Database Backups of Type: {resourceGroup.DbType}"); + //Finally Update Status + bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.EXECUTING.ToString()); + if (updated) + _logger.LogInformation($"Processing Queued Backup Record Key: #{backupRecord.Id}...SUCCESS"); else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); + _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{backupRecord.Id} status"); } - + else + _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); } + } - //Check if Any Delete - if (scheduleToDelete.Count > 0) - foreach (var rm in scheduleToDelete) - await backupRecordPersistanceService.RemoveAsync(rm); } - + //Check if Any Delete + if (scheduleToDelete.Count > 0) + foreach (var rm in scheduleToDelete) + await backupRecordPersistanceService.RemoveAsync(rm); } } @@ -127,32 +124,26 @@ private void SetupBackgroundRemovedExpiredBackupsService(CancellationToken cance { while (!cancellationToken.IsCancellationRequested) { -#if DEBUG await Task.Delay(3000); //Runs After 3sec -#else - await Task.Delay(60000); //Runs After 1 Minute -#endif try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI Injections + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); + if (expiredBackups != null && expiredBackups.Count > 0) { - //DI Injections - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); - if (expiredBackups != null && expiredBackups.Count > 0) - { - foreach (BackupRecord rm in expiredBackups.Take(50).ToList()) - if (!await backupRecordPersistanceService.RemoveAsync(rm.Id)) - _logger.LogWarning($"Unable to delete Expired Backup Record: {rm.Id}"); - else - { - _logger.LogInformation($"Removed Expired Backup Record, Id: {rm.Id}"); - //Options InDepth Delete - if (_persistanceOptions.InDepthBackupRecordDeleteEnabled) - await StartInDepthDeleteForAsync(rm); - } - } + foreach (BackupRecord rm in expiredBackups.Take(50).ToList()) + if (!await backupRecordPersistanceService.RemoveAsync(rm.Id)) + _logger.LogWarning($"Unable to delete Expired Backup Record: {rm.Id}"); + else + { + _logger.LogInformation($"Removed Expired Backup Record, Id: {rm.Id}"); + //Options InDepth Delete + if (_persistanceOptions.InDepthBackupRecordDeleteEnabled) + await StartInDepthDeleteForAsync(rm); + } } } catch (Exception ex) @@ -170,33 +161,36 @@ private async Task StartInDepthDeleteForAsync(BackupRecord rm) { if (rm == null) return; //scope - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IContentDeliveryRecordRepository contentDeliveryRecordsService = scope.ServiceProvider.GetRequiredService(); + BotsManagerBackgroundJob botsManagerBackgroundJob = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); + //get db information + BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(rm.BackupDatabaseInfoId); + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); + if (resourceGroup == null) + return; + //Proceed + List dbRecords = await contentDeliveryRecordsService.GetAllByBackupRecordIdAsync(rm.Id); //database record content delivery + if (dbRecords == null) + return; + List supportedInDepthDelete = [BackupDeliveryConfigTypes.Dropbox.ToString(), BackupDeliveryConfigTypes.AzureBlobStorage.ToString()]; + List supportedDeliveryRecords = [.. dbRecords.Where(x => supportedInDepthDelete.Contains(x.DeliveryType))]; + if (supportedDeliveryRecords == null || supportedDeliveryRecords.Count == 0) + return; + foreach (BackupRecordDelivery rec in supportedDeliveryRecords) { - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IContentDeliveryRecordRepository contentDeliveryRecordsService = scope.ServiceProvider.GetRequiredService(); - BotsManagerBackgroundJob botsManagerBackgroundJob = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); - //get db information - BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(rm.BackupDatabaseInfoId); - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); - if (resourceGroup == null) - return; - //Proceed - var dbRecords = await contentDeliveryRecordsService.GetAllByBackupRecordIdAsync(rm.Id); //database record content delivery - if (dbRecords == null) - return; - List supportedInDepthDelete = new List { BackupDeliveryConfigTypes.Dropbox.ToString(), BackupDeliveryConfigTypes.AzureBlobStorage.ToString() }; - List supportedDeliveryRecords = dbRecords.Where(x => supportedInDepthDelete.Contains(x.DeliveryType)).ToList(); - if (supportedDeliveryRecords == null || supportedDeliveryRecords.Count == 0) - return; - foreach (var rec in supportedDeliveryRecords) + if (rec.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) { - if (rec.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) - { - //In Depth Remove From DropBox - botsManagerBackgroundJob.AddBot(new InDepthDeleteDropboxBot(resourceGroup, rm, rec, _serviceScopeFactory)); - } + //In Depth Remove From DropBox + botsManagerBackgroundJob.AddBot(new InDepthDeleteDropboxBot(resourceGroup, rm, rec, _serviceScopeFactory)); + } + else if (rec.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) + { + //In Depth Remove From Object Storage + botsManagerBackgroundJob.AddBot(new InDepthDeleteObjectStorageBot(resourceGroup, rm, rec, _serviceScopeFactory)); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs index e6bdfbe..d700a36 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupBackgroundZIPJob.cs @@ -42,60 +42,58 @@ public Task StopAsync(CancellationToken cancellationToken) private void SetupBackgroundService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new Thread(async () => { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(7000); + await Task.Delay(7000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); + //Proceed + List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordStatus.COMPLETED.ToString()); + if (queuedBackups != null && queuedBackups.Count > 0) { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); - //Proceed - List queuedBackups = await backupRecordPersistanceService.GetAllByStatusAsync(BackupRecordBackupStatus.COMPLETED.ToString()); - if (queuedBackups != null && queuedBackups.Count > 0) + foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) { - foreach (BackupRecord backupRecord in queuedBackups.OrderBy(x => x.RegisteredDateUTC).ToList()) + //get valid database + BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); + if (resourceGroup != null) { - //get valid database - BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); - if (resourceGroup != null) + //Use Resource Group Threads + if (resourceGroup.CompressBackupFiles) { - //Use Resource Group Threads - if (resourceGroup.CompressBackupFiles) + //Check Resource Group Maximum Threads + if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) { - //Check Resource Group Maximum Threads - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}..."); - //Add to Queue - _botsManagerBackgroundJob.AddBot(new BackupZippingRobot(resourceGroup.Id, backupRecord, _serviceScopeFactory)); - bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordBackupStatus.COMPRESSING.ToString()); - if (updated) - _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}...SUCCESS"); - else - _logger.LogWarning($"Queued for Zipping But Failed to Update Status for Backup Record Key: #{backupRecord.Id}"); - } - } - else - { - _logger.LogInformation($">> Skipping Compression for Database Record Key: #{backupRecord.Id}..."); - bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordBackupStatus.READY.ToString()); + _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}..."); + //Add to Queue + _botsManagerBackgroundJob.AddBot(new BackupZippingRobot(resourceGroup.Id, backupRecord, _serviceScopeFactory)); + bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.COMPRESSING.ToString()); if (updated) - _logger.LogInformation($">> Skipped Compression and Completed Backup Updated Record Key: #{backupRecord.Id}...SUCCESS"); + _logger.LogInformation($"Queueing Zip Database Record Key: #{backupRecord.Id}...SUCCESS"); else - _logger.LogWarning($"Failed to Update Status as READY for Backup Record Key: #{backupRecord.Id}"); + _logger.LogWarning($"Queued for Zipping But Failed to Update Status for Backup Record Key: #{backupRecord.Id}"); } } else - _logger.LogWarning($"The Backup Record Id: {backupRecord.Id}, doesn't seem to have been assigned to a valid Resource Group, Zipping Skipped"); + { + _logger.LogInformation($">> Skipping Compression for Database Record Key: #{backupRecord.Id}..."); + bool updated = await backupRecordPersistanceService.UpdateStatusFeedAsync(backupRecord.Id, BackupRecordStatus.READY.ToString()); + if (updated) + _logger.LogInformation($">> Skipped Compression and Completed Backup Updated Record Key: #{backupRecord.Id}...SUCCESS"); + else + _logger.LogWarning($"Failed to Update Status as READY for Backup Record Key: #{backupRecord.Id}"); + } } + else + _logger.LogWarning($"The Backup Record Id: {backupRecord.Id}, doesn't seem to have been assigned to a valid Resource Group, Zipping Skipped"); } } } @@ -103,7 +101,6 @@ private void SetupBackgroundService(CancellationToken cancellationToken) { _logger.LogError(ex.Message); } - } }); t.Start(); diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs index b270d0a..af9e659 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliveryDispatchBackgroundJob.cs @@ -20,9 +20,9 @@ public class BackupRecordDeliveryDispatchBackgroundJob : IHostedService public BackupRecordDeliveryDispatchBackgroundJob(ILogger logger, IServiceScopeFactory serviceScopeFactory, BotsManagerBackgroundJob botsManagerBackgroundJob) { - this._logger = logger; - this._serviceScopeFactory = serviceScopeFactory; - this._botsManagerBackgroundJob = botsManagerBackgroundJob; + _logger = logger; + _serviceScopeFactory = serviceScopeFactory; + _botsManagerBackgroundJob = botsManagerBackgroundJob; } public Task StartAsync(CancellationToken cancellationToken) @@ -40,101 +40,103 @@ public Task StopAsync(CancellationToken cancellationToken) private void SetupBackgroundService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new Thread(async () => { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(10000); + await Task.Delay(10000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List contentDeliveryRecords = await contentDeliveryRecordPersistanceService.GetAllByStatusAsync(BackupRecordDeliveryStatus.QUEUED.ToString()); + if (contentDeliveryRecords != null && contentDeliveryRecords.Count > 0) { - //DI INJECTIONS - IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List contentDeliveryRecords = await contentDeliveryRecordPersistanceService.GetAllByStatusAsync(BackupRecordDeliveryStatus.QUEUED.ToString()); - if (contentDeliveryRecords != null && contentDeliveryRecords.Count > 0) + List scheduleToDeleteRecords = []; + foreach (BackupRecordDelivery contentDeliveryRecord in contentDeliveryRecords.OrderBy(x => x.RegisteredDateUTC).ToList()) { - List scheduleToDeleteRecords = new List(); - foreach (BackupRecordDelivery contentDeliveryRecord in contentDeliveryRecords.OrderBy(x => x.RegisteredDateUTC).ToList()) - { - _logger.LogInformation($"Processing Queued Content Delivery Record: #{contentDeliveryRecord.Id}..."); - BackupRecord backupRecordInfo = await backupRecordPersistanceService.GetByIdAsync(contentDeliveryRecord?.BackupRecordId ?? 0); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecordInfo?.BackupDatabaseInfoId); - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo?.ResourceGroupId); + _logger.LogInformation($"Processing Queued Content Delivery Record: #{contentDeliveryRecord.Id}..."); + BackupRecord backupRecordInfo = await backupRecordPersistanceService.GetByIdAsync(contentDeliveryRecord?.BackupRecordId ?? 0); + BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecordInfo?.BackupDatabaseInfoId); + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo?.ResourceGroupId); - if (backupRecordInfo == null) - { - _logger.LogWarning($"No Backup Record with Id: {contentDeliveryRecord.BackupRecordId}, Content Delivery Record will be Deleted: {contentDeliveryRecord.Id}"); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - else if (resourceGroup == null) - { - _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Resource Group, Will be Removed"); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - else if (resourceGroup.BackupDeliveryConfig == null) - { - _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Configuration, Will be Removed"); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - else + if (backupRecordInfo == null) + { + _logger.LogWarning($"No Backup Record with Id: {contentDeliveryRecord.BackupRecordId}, Content Delivery Record will be Deleted: {contentDeliveryRecord.Id}"); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else if (resourceGroup == null) + { + _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Resource Group, Will be Removed"); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else if (resourceGroup.BackupDeliveryConfig == null) + { + _logger.LogWarning($"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has no valid Configuration, Will be Removed"); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + else + { + //Override Maximum Running Threads// This is because of currently being used exception + if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, 1)) { - //Override Maximum Running Threads// This is because of currently being used exception - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, 1)) + string status = BackupRecordDeliveryStatus.EXECUTING.ToString(); + string statusMsg = "Dispatching Backup Record"; + if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.DownloadLink.ToString()) { - string status = BackupRecordDeliveryStatus.EXECUTING.ToString(); - string statusMsg = "Dispatching Backup Record"; - if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.DownloadLink.ToString()) - { - //Download Link Generator - _botsManagerBackgroundJob.AddBot(new UploaderLinkGenBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) - { - //FTP Uploader - _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) - { - //Email Send and Uploader - _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) - { - //Azure Blob Storage - _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); - } - else - { - status = BackupRecordDeliveryStatus.ERROR.ToString(); - statusMsg = $"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has UNSUPPORTED Delivery Type, Record Will be Removed"; - _logger.LogWarning(statusMsg); - scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); - } - //Finally Update Status - bool updated = await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); - if (!updated) - _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{contentDeliveryRecord.Id} status"); + //Download Link Generator + _botsManagerBackgroundJob.AddBot(new UploaderLinkGenBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Ftp.ToString()) + { + //FTP Uploader + _botsManagerBackgroundJob.AddBot(new UploaderFTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Smtp.ToString()) + { + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderEmailSMTPBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.Dropbox.ToString()) + { + //Email Send and Uploader + _botsManagerBackgroundJob.AddBot(new UploaderDropboxBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.AzureBlobStorage.ToString()) + { + //Azure Blob Storage + _botsManagerBackgroundJob.AddBot(new UploaderAzureStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); + } + else if (contentDeliveryRecord.DeliveryType == BackupDeliveryConfigTypes.ObjectStorage.ToString()) + { + //Object Storage + _botsManagerBackgroundJob.AddBot(new UploaderObjectStorageBot(resourceGroup, backupRecordInfo, contentDeliveryRecord, _serviceScopeFactory)); } else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); + { + status = BackupRecordDeliveryStatus.ERROR.ToString(); + statusMsg = $"Backup Record Id: {contentDeliveryRecord.BackupRecordId}, Queued for Content Delivery has UNSUPPORTED Delivery Type, Record Will be Removed"; + _logger.LogWarning(statusMsg); + scheduleToDeleteRecords.Add(contentDeliveryRecord.Id); + } + //Finally Update Status + bool updated = await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(contentDeliveryRecord.Id, status, statusMsg); + if (!updated) + _logger.LogWarning($"Queued for Backup but was unable to update backup record Key: #{contentDeliveryRecord.Id} status"); } + else + _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} Bots are Busy, Running Bots Count: {resourceGroup.MaximumRunningBots}, waiting for available Bots...."); } - //Check if Any Delete - if (scheduleToDeleteRecords.Count > 0) - foreach (var rm in scheduleToDeleteRecords) - await contentDeliveryRecordPersistanceService.RemoveAsync(rm); } - + //Check if Any Delete + if (scheduleToDeleteRecords.Count > 0) + foreach (var rm in scheduleToDeleteRecords) + await contentDeliveryRecordPersistanceService.RemoveAsync(rm); } } catch (Exception ex) @@ -149,31 +151,29 @@ private void SetupBackgroundService(CancellationToken cancellationToken) private void SetupBackgroundRemovedExpiredBackupsService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new Thread(async () => { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(60000); //Runs After 1 Minute + await Task.Delay(60000, cancellationToken); //Runs After 1 Minute try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); + if (expiredBackups != null && expiredBackups.Count > 0) { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List expiredBackups = await backupRecordPersistanceService.GetAllExpiredAsync(); - if (expiredBackups != null && expiredBackups.Count > 0) - { - List toDeleteList = new List(); - foreach (BackupRecord backupRecord in expiredBackups) - toDeleteList.Add(backupRecord.Id); - _logger.LogInformation($"Queued ({expiredBackups.Count}) Expired Records for Delete"); - //Check if Any Delete - if (toDeleteList.Count > 0) - foreach (var rm in toDeleteList) - if (!(await backupRecordPersistanceService.RemoveAsync(rm))) - _logger.LogWarning("Unable to delete Expired Backup Record"); - } + List toDeleteList = new(); + foreach (BackupRecord backupRecord in expiredBackups) + toDeleteList.Add(backupRecord.Id); + _logger.LogInformation($"Queued ({expiredBackups.Count}) Expired Records for Delete"); + //Check if Any Delete + if (toDeleteList.Count > 0) + foreach (var rm in toDeleteList) + if (!(await backupRecordPersistanceService.RemoveAsync(rm))) + _logger.LogWarning("Unable to delete Expired Backup Record"); } } catch (Exception ex) diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs index ae3d34c..5b11b34 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupRecordDeliverySchedulerBackgroundJob.cs @@ -16,13 +16,12 @@ public class BackupRecordDeliverySchedulerBackgroundJob : IHostedService { private readonly ILogger _logger; private readonly IServiceScopeFactory _serviceScopeFactory; - public BackupRecordDeliverySchedulerBackgroundJob( - ILogger logger, - IServiceScopeFactory serviceScopeFactory) + public BackupRecordDeliverySchedulerBackgroundJob(ILogger logger, IServiceScopeFactory serviceScopeFactory) { this._logger = logger; this._serviceScopeFactory = serviceScopeFactory; } + public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting service...."); @@ -41,69 +40,68 @@ private void SetupBackgroundService(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(4000); + await Task.Delay(4000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List pendingExecutionRecords = await backupRecordPersistanceService.GetAllReadyAndPendingDeliveryAsync(); + foreach (BackupRecord backupRecord in pendingExecutionRecords?.OrderBy(x => x.RegisteredDateUTC)?.ToList()) { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List pendingExecutionRecords = await backupRecordPersistanceService.GetAllReadyAndPendingDeliveryAsync(); - foreach (BackupRecord backupRecord in pendingExecutionRecords?.OrderBy(x => x.RegisteredDateUTC)?.ToList()) - { - _logger.LogInformation($"Queueing Content Delivery for Backup Record Id: {backupRecord.Id}..."); - //## get other services - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); - //get db information - BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - //Check if valid Resource Group - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); - //Has Valid Resource Group + _logger.LogInformation($"Queueing Content Delivery for Backup Record Id: {backupRecord.Id}..."); + //## get other services + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoRepository = scope.ServiceProvider.GetRequiredService(); + //get db information + BackupDatabaseInfo backupRecordDbInfo = await databaseInfoRepository.GetByIdAsync(backupRecord.BackupDatabaseInfoId); + //Check if valid Resource Group + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupRecordDbInfo?.ResourceGroupId ?? string.Empty); + //Has Valid Resource Group - //check if backup delivery config is set - if (resourceGroup.BackupDeliveryConfig == null) - { - _logger.LogInformation($"Resource Group Id: {backupRecord.Id}, doesn't have any backup delivery config, Skipped"); - _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SKIPPED_EXECUTION.ToString()); - } - else + //check if backup delivery config is set + if (resourceGroup.BackupDeliveryConfig == null) + { + _logger.LogInformation($"Resource Group Id: {backupRecord.Id}, doesn't have any backup delivery config, Skipped"); + _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SKIPPED_EXECUTION.ToString()); + } + else + { + //loop delivery types + foreach (BackupDeliveryConfigTypes deliveryType in Enum.GetValues(typeof(BackupDeliveryConfigTypes))) { - //loop delivery types - foreach (BackupDeliveryConfigTypes deliveryType in Enum.GetValues(typeof(BackupDeliveryConfigTypes))) + bool isDeliveryEnabled = false; + switch (deliveryType) { - bool isDeliveryEnabled = false; - switch (deliveryType) - { - case BackupDeliveryConfigTypes.DownloadLink: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.DownloadLink?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.Ftp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Ftp?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.Smtp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Smtp?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.Dropbox: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Dropbox?.IsEnabled ?? false; break; - case BackupDeliveryConfigTypes.AzureBlobStorage: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.AzureBlobStorage?.IsEnabled ?? false; break; - default: isDeliveryEnabled = false; break; - } - //check if enabled - if (isDeliveryEnabled) + case BackupDeliveryConfigTypes.DownloadLink: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.DownloadLink?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.Ftp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Ftp?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.Smtp: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Smtp?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.Dropbox: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.Dropbox?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.AzureBlobStorage: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.AzureBlobStorage?.IsEnabled ?? false; break; + case BackupDeliveryConfigTypes.ObjectStorage: isDeliveryEnabled = resourceGroup.BackupDeliveryConfig.ObjectStorage?.IsEnabled ?? false; break; + default: isDeliveryEnabled = false; break; + } + //check if enabled + if (isDeliveryEnabled) + { + bool queuedSuccess = await contentDeliveryRecordPersistanceService.AddOrUpdateAsync(new BackupRecordDelivery { - bool queuedSuccess = await contentDeliveryRecordPersistanceService.AddOrUpdateAsync(new BackupRecordDelivery - { - Id = $"{backupRecord.Id}|{resourceGroup.Id}|{deliveryType}".ToMD5String().ToUpper(), //Unique Identification - BackupRecordId = backupRecord.Id, - CurrentStatus = BackupRecordDeliveryStatus.QUEUED.ToString(), - DeliveryType = deliveryType.ToString(), - RegisteredDateUTC = DateTime.UtcNow, - StatusUpdateDateUTC = DateTime.UtcNow, - ExecutionMessage = "Queued for Dispatch" - }); - if (!queuedSuccess) - _logger.LogWarning($"unable to queue Backup Record Id: {backupRecord.Id} for delivery via : {deliveryType}, resource group: {resourceGroup.Name}"); - } + Id = $"{backupRecord.Id}|{resourceGroup.Id}|{deliveryType}".ToMD5String().ToUpper(), //Unique Identification + BackupRecordId = backupRecord.Id, + CurrentStatus = BackupRecordDeliveryStatus.QUEUED.ToString(), + DeliveryType = deliveryType.ToString(), + RegisteredDateUTC = DateTime.UtcNow, + StatusUpdateDateUTC = DateTime.UtcNow, + ExecutionMessage = "Queued for Dispatch" + }); + if (!queuedSuccess) + _logger.LogWarning($"unable to queue Backup Record Id: {backupRecord.Id} for delivery via : {deliveryType}, resource group: {resourceGroup.Name}"); } - //Update Execution - _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SUCCESSFULLY_EXECUTED.ToString()); } + //Update Execution + _ = await backupRecordPersistanceService.UpdateDeliveryRunnedAsync(backupRecord.Id, true, BackupRecordExecutedDeliveryRunStatus.SUCCESSFULLY_EXECUTED.ToString()); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs index 5c81f12..17e4032 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BackupSchedulerBackgroundJob.cs @@ -30,10 +30,6 @@ public BackupSchedulerBackgroundJob( this._persistanceOptions = persistanceOptions; this._serviceScopeFactory = serviceScopeFactory; this._botsManagerBackgroundJob = botsManagerBackgroundJob; - } - public void Initialize() - { - } public Task StartAsync(CancellationToken cancellationToken) { @@ -53,78 +49,75 @@ private void SetupBackgroundService(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(3000); + await Task.Delay(3000, cancellationToken); try { - using (var scope = _serviceScopeFactory.CreateScope()) + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupScheduleRepository backupSchedulePersistanceService = scope.ServiceProvider.GetRequiredService(); + IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); + IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + DateTime currentTimeUTC = DateTime.UtcNow; + List dueSchedules = await backupSchedulePersistanceService.GetAllDueByDateAsync(); + if (dueSchedules != null && dueSchedules.Count > 0) { - //DI INJECTIONS - IBackupScheduleRepository backupSchedulePersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - DateTime currentTimeUTC = DateTime.UtcNow; - List dueSchedules = await backupSchedulePersistanceService.GetAllDueByDateAsync(); - if (dueSchedules != null && dueSchedules.Count > 0) + List scheduleToDelete = new List(); + foreach (BackupSchedule schedule in dueSchedules.OrderBy(x => x.NextRunUTC).ToList()) { - List scheduleToDelete = new List(); - foreach (BackupSchedule schedule in dueSchedules.OrderBy(x => x.NextRunUTC).ToList()) + _logger.LogInformation($"Queueing Scheduled Backup..."); + BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(schedule.BackupDatabaseInfoId); + if (backupDatabaseInfo == null) { - _logger.LogInformation($"Queueing Scheduled Backup..."); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(schedule.BackupDatabaseInfoId); - if (backupDatabaseInfo == null) + _logger.LogWarning($"No Database Info matches with Id: {schedule.BackupDatabaseInfoId}, Schedule Record will be Deleted: {schedule.Id}"); + scheduleToDelete.Add(schedule.Id); + } + else + { + //Proceed + ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); + if (resourceGroup == null) { - _logger.LogWarning($"No Database Info matches with Id: {schedule.BackupDatabaseInfoId}, Schedule Record will be Deleted: {schedule.Id}"); + _logger.LogWarning($"Can NOT queue Database for Backup Id: {backupDatabaseInfo.Id}, Reason: Assigned Resource Group doen't exist, Resource Group Id: {backupDatabaseInfo.Id}, Schedule will be Removed"); scheduleToDelete.Add(schedule.Id); } else { - //Proceed - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); - if (resourceGroup == null) + //has valid Resource Group Proceed + DateTime RecordExpiryUTC = currentTimeUTC.AddDays(resourceGroup.BackupExpiryAgeInDays); + BackupRecord newRecord = new BackupRecord { - _logger.LogWarning($"Can NOT queue Database for Backup Id: {backupDatabaseInfo.Id}, Reason: Assigned Resource Group doen't exist, Resource Group Id: {backupDatabaseInfo.Id}, Schedule will be Removed"); - scheduleToDelete.Add(schedule.Id); - } - else - { - //has valid Resource Group Proceed - DateTime RecordExpiryUTC = currentTimeUTC.AddDays(resourceGroup.BackupExpiryAgeInDays); - BackupRecord newRecord = new BackupRecord - { - BackupDatabaseInfoId = schedule.BackupDatabaseInfoId, - BackupStatus = BackupRecordBackupStatus.QUEUED.ToString(), - ExpiryDateUTC = RecordExpiryUTC, - Name = $"{backupDatabaseInfo.DatabaseName} on {resourceGroup.DbServer}", - Path = Path.Combine(_persistanceOptions.DefaultBackupDirectory, resourceGroup.GetSavingPathFromFormat(backupDatabaseInfo.DatabaseName, _persistanceOptions.BackupFileSaveFormat, currentTimeUTC)), - StatusUpdateDateUTC = currentTimeUTC, - RegisteredDateUTC = currentTimeUTC, - ExecutedDeliveryRun = false - }; - - bool addedSuccess = await backupRecordPersistanceService.AddOrUpdateAsync(newRecord); - if (!addedSuccess) - throw new Exception("Unable to Queue Database for Backup"); - else - _logger.LogInformation($"Queueing Scheduled Backup...SUCCESS"); - //Update Schedule - bool updatedSchedule = await backupSchedulePersistanceService.UpdateLastRunAsync(schedule.Id, currentTimeUTC); - if (!updatedSchedule) - _logger.LogWarning("Unable to Update Scheduled Next Run"); - //Buy Some Seconds to avoid Conflict Name - await Task.Delay(new Random().Next(100)); - } + BackupDatabaseInfoId = schedule.BackupDatabaseInfoId, + BackupStatus = BackupRecordStatus.QUEUED.ToString(), + ExpiryDateUTC = RecordExpiryUTC, + Name = $"{backupDatabaseInfo.DatabaseName} on {resourceGroup.DbServer}", + Path = Path.Combine(_persistanceOptions.DefaultBackupDirectory, resourceGroup.GetSavingPathFromFormat(backupDatabaseInfo.DatabaseName, _persistanceOptions.BackupFileSaveFormat, currentTimeUTC)), + StatusUpdateDateUTC = currentTimeUTC, + RegisteredDateUTC = currentTimeUTC, + ExecutedDeliveryRun = false + }; + bool addedSuccess = await backupRecordPersistanceService.AddOrUpdateAsync(newRecord); + if (!addedSuccess) + throw new Exception("Unable to Queue Database for Backup"); + else + _logger.LogInformation($"Queueing Scheduled Backup...SUCCESS"); + //Update Schedule + bool updatedSchedule = await backupSchedulePersistanceService.UpdateLastRunAsync(schedule.Id, currentTimeUTC); + if (!updatedSchedule) + _logger.LogWarning("Unable to Update Scheduled Next Run"); + //Buy Some Seconds to avoid Conflict Name + await Task.Delay(new Random().Next(100)); } } - //Check if Any Delete - if (scheduleToDelete.Count > 0) - foreach (var rm in scheduleToDelete) - await backupSchedulePersistanceService.RemoveAsync(rm); - } + } + //Check if Any Delete + if (scheduleToDelete.Count > 0) + foreach (var rm in scheduleToDelete) + await backupSchedulePersistanceService.RemoveAsync(rm); } } catch (Exception ex) @@ -140,41 +133,39 @@ private void SetupBackgroundNonResponsiveStopService(CancellationToken cancellat { var t = new Thread(async () => { - List statusChecks = new List { BackupRecordBackupStatus.EXECUTING.ToString(), BackupRecordBackupStatus.COMPRESSING.ToString(), BackupRecordDeliveryStatus.EXECUTING.ToString() }; + List statusChecks = new List { BackupRecordStatus.EXECUTING.ToString(), BackupRecordStatus.COMPRESSING.ToString(), BackupRecordDeliveryStatus.EXECUTING.ToString() }; int executionTimeoutInMinutes = _persistanceOptions.ExecutionTimeoutInMinutes < 1 ? 1 : _persistanceOptions.ExecutionTimeoutInMinutes; while (!cancellationToken.IsCancellationRequested) { try { - using (var scope = _serviceScopeFactory.CreateScope()) - { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List botsToRemove = new List(); - //REMOVE BACKUP RECORDS - List recordsIds = await backupRecordPersistanceService.GetAllNoneResponsiveIdsAsync(statusChecks, executionTimeoutInMinutes); - if (recordsIds != null && recordsIds.Count > 0) - foreach (long id in recordsIds) - { - await backupRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordBackupStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); - botsToRemove.Add(id.ToString()); - } + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + //DI INJECTIONS + IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + IContentDeliveryRecordRepository contentDeliveryRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); + //Proceed + List botsToRemove = []; + //REMOVE BACKUP RECORDS + List recordsIds = await backupRecordPersistanceService.GetAllNoneResponsiveIdsAsync(statusChecks, executionTimeoutInMinutes); + if (recordsIds != null && recordsIds.Count > 0) + foreach (long id in recordsIds) + { + await backupRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); + botsToRemove.Add(id.ToString()); + } - //REMOVE CONTENT DELIVERY RECORDS - List deliveryRecordIds = await contentDeliveryRecordPersistanceService.GetAllNoneResponsiveAsync(statusChecks, executionTimeoutInMinutes); - if (deliveryRecordIds != null && deliveryRecordIds.Count > 0) - foreach (string id in deliveryRecordIds) - { - await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordBackupStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); - botsToRemove.Add(id); - } + //REMOVE CONTENT DELIVERY RECORDS + List deliveryRecordIds = await contentDeliveryRecordPersistanceService.GetAllNoneResponsiveAsync(statusChecks, executionTimeoutInMinutes); + if (deliveryRecordIds != null && deliveryRecordIds.Count > 0) + foreach (string id in deliveryRecordIds) + { + await contentDeliveryRecordPersistanceService.UpdateStatusFeedAsync(id, BackupRecordStatus.ERROR.ToString(), "Bot Execution Timeout", executionTimeoutInMinutes); + botsToRemove.Add(id); + } - //Finally Try And Stop - if (botsToRemove.Count > 0) - _botsManagerBackgroundJob.TerminateBots(botsToRemove); - } + //Finally Try And Stop + if (botsToRemove.Count > 0) + _botsManagerBackgroundJob.TerminateBots(botsToRemove); } catch (Exception ex) { _logger.LogWarning($"Stopping Non Responsive Services Error: {ex.Message}"); } //Delay diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs index f256cfe..06da2a3 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/BackupZippingBot.cs @@ -1,7 +1,6 @@ using ICSharpCode.SharpZipLib.Zip; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; @@ -17,44 +16,44 @@ internal class BackupZippingRobot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroupId; - - public string BotId => _backupRecord.Id.ToString(); - public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroupId}::{_backupRecord.Id}::{nameof(BackupZippingRobot)}"; + public string ResourceGroupId => _resourceGroupId; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; public BackupZippingRobot(string resourceGroupId, BackupRecord backupRecord, IServiceScopeFactory scopeFactory) { - this._resourceGroupId = resourceGroupId; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; - //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + _resourceGroupId = resourceGroupId; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Zip of Db: {_backupRecord.Path}"); - CheckIfFileExistsOrRemove(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating zip of: {Path}", _backupRecord.Path); + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or may have been deleted, Path: {_backupRecord.Path}"); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); stopwatch.Start(); - + Status = BotStatus.Running; + //proceed string newZIPPath = _backupRecord.Path.Replace(".bak", ".zip"); - using (ZipOutputStream s = new ZipOutputStream(File.Create(newZIPPath))) + using (ZipOutputStream s = new(File.Create(newZIPPath))) { s.SetLevel(9); // 0 - store only to 9 - means best compression byte[] buffer = new byte[4096]; - var entry = new ZipEntry(Path.GetFileName(_backupRecord.Path)); - entry.DateTime = DateTime.UtcNow; + ZipEntry entry = new(Path.GetFileName(_backupRecord.Path)) + { + DateTime = DateTime.UtcNow + }; s.PutNextEntry(entry); using (FileStream fs = File.OpenRead(_backupRecord.Path)) { @@ -70,40 +69,35 @@ public async Task RunAsync() } stopwatch.Stop(); TryDeleteOldFile(_backupRecord.Path); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.READY.ToString(), "Successfull & Ready", stopwatch.ElapsedMilliseconds, newZIPPath); - _logger.LogInformation($"Creating Zip of Db: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = null, + Status = BackupRecordStatus.READY, + Message = "Successfull & Ready", + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, + NewFilePath = newZIPPath, + }, cancellationToken); + _logger.LogInformation("successfully zipped file: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { + Status = BotStatus.Error; this._logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(long recordId, string status, string message, long elapsed, string newZIPPath = null) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed, newZIPPath); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + DeliveryFeedType = DeliveryFeedType.BackupNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = null, + Status = BackupRecordStatus.ERROR, + Message = ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, + }, cancellationToken); } } @@ -132,9 +126,11 @@ private void TryDeleteOldFile(string path) } } while (!success); - } - catch (Exception ex) { this._logger.LogWarning($"The File Name Failed to Delete,Error: {ex.Message}, File: {path}"); } + catch (Exception ex) + { + this._logger.LogWarning("The File Name Failed to Delete, Error: {Message}, File: {Path}", ex.Message, path); + } } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs index 38d8014..d722d44 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteDropboxBot.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -16,60 +17,59 @@ internal class InDepthDeleteDropboxBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(InDepthDeleteDropboxBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public InDepthDeleteDropboxBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"DELETING Backup File From DropBox...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("Deleting backup file from DropBox: {Path}, Id: {Id}", this._backupRecord.Path, _contentDeliveryRecord.Id); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); DropboxDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Dropbox ?? throw new Exception("no valid dropbox config"); stopwatch.Start(); + Status = BotStatus.Running; //Directory - string validDirectory = (string.IsNullOrWhiteSpace(settings.Directory)) ? "/" : settings.Directory; - validDirectory = (validDirectory.EndsWith("/")) ? validDirectory : validDirectory + "/"; - validDirectory = (validDirectory.StartsWith("/")) ? validDirectory : "/" + validDirectory; + string validDirectory = string.IsNullOrWhiteSpace(settings.Directory) ? "/" : settings.Directory; + validDirectory = validDirectory.EndsWith('/') ? validDirectory : validDirectory + "/"; + validDirectory = validDirectory.StartsWith('/') ? validDirectory : "/" + validDirectory; //Filename string fileName = Path.GetFileName(this._backupRecord.Path); //Proceed if (string.IsNullOrWhiteSpace(settings.AccessToken)) throw new Exception("Access Token is NULL"); //Proceed - using (DropboxClient dbx = new DropboxClient(settings.AccessToken.Trim())) + using (DropboxClient dbx = new(settings.AccessToken.Trim())) { string initialFileName = string.Format("{0}{1}", validDirectory, fileName); Dropbox.Api.Files.DeleteResult delResponse = await dbx.Files.DeleteV2Async(initialFileName, null); } stopwatch.Stop(); - _logger.LogInformation($"DELETING Backup File From DropBox: {_backupRecord.Path}... SUCCESS"); + _logger.LogInformation("Successfully deleted Backup File From DropBox: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { + Status = BotStatus.Error; this._logger.LogWarning(ex.Message); stopwatch.Stop(); } - finally - { - this.IsCompleted = true; - } } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs new file mode 100644 index 0000000..6c03c31 --- /dev/null +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/InDepthDeleteObjectStorageBot.cs @@ -0,0 +1,72 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Minio; +using Minio.DataModel.Args; +using SemanticBackup.Core.Models; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots +{ + internal class InDepthDeleteObjectStorageBot : IBot + { + private readonly BackupRecordDelivery _contentDeliveryRecord; + private readonly ResourceGroup _resourceGroup; + private readonly BackupRecord _backupRecord; + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(InDepthDeleteObjectStorageBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + + public InDepthDeleteObjectStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) + { + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; + //Logger + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); + } + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) + { + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); + try + { + _logger.LogInformation("deleting uploaded file from ObjectStorage: {Path}, Id: {Id}", _backupRecord.Path, _contentDeliveryRecord.Id); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); + ObjectStorageDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.ObjectStorage ?? throw new Exception("no valid object storage config"); + stopwatch.Start(); + Status = BotStatus.Running; + //Container + string validBucket = string.IsNullOrWhiteSpace(settings.Bucket) ? "backups" : settings.Bucket; + //Filename + string fileName = Path.GetFileName(this._backupRecord.Path); + //Proceed + using IMinioClient minioClient = new MinioClient().WithEndpoint(settings.Server, settings.Port).WithCredentials(settings.AccessKey, settings.SecretKey).WithSSL(settings.UseSsl).Build(); + { + await minioClient.RemoveObjectAsync(new RemoveObjectArgs() + .WithBucket(validBucket) + .WithObject(fileName), cancellationToken); + } + stopwatch.Stop(); + _logger.LogInformation("Successfully deleted file from ObjectStorage: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; + } + catch (Exception ex) + { + Status = BotStatus.Error; + _logger.LogError(ex.Message); + stopwatch.Stop(); + } + } + } +} diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs index 2310a45..15758df 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/MySQLBackupBot.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -16,79 +17,74 @@ internal class MySQLBackupBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _backupRecord.Id.ToString(); public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(MySQLBackupBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public MySQLBackupBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IServiceScopeFactory serviceScopeFactory) { - this._databaseName = databaseName; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = serviceScopeFactory; + _databaseName = databaseName; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = serviceScopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Backup of Db: {_databaseName}"); - EnsureFolderExists(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating backup of Db: {_databaseName}", _databaseName); + string directory = Path.GetDirectoryName(_backupRecord.Path); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); stopwatch.Start(); + Status = BotStatus.Running; //Execute Service bool backupedUp = false; - using (var scope = _scopeFactory.CreateScope()) + using (IServiceScope scope = _scopeFactory.CreateScope()) { IBackupProviderForMySQLServer providerService = scope.ServiceProvider.GetRequiredService(); backupedUp = await providerService.BackupDatabaseAsync(_databaseName, _resourceGroup, _backupRecord); } stopwatch.Stop(); - if (backupedUp) - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.COMPLETED.ToString(), "Successfull", stopwatch.ElapsedMilliseconds); - else + if (!backupedUp) throw new Exception("Creating Backup Failed to Return Success Completion"); - _logger.LogInformation($"Creating Backup of Db: {_databaseName}...SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = null, + Status = BackupRecordStatus.COMPLETED, + Message = "Successfull", + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + + _logger.LogInformation("Successfully Backup of Db: {_databaseName}", _databaseName); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void EnsureFolderExists(string path) - { - string directory = Path.GetDirectoryName(path); - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); - } - - private void UpdateBackupFeed(long recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + DeliveryFeedType = DeliveryFeedType.BackupNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = null, + Status = BackupRecordStatus.ERROR, + Message = ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs index be73de6..dbbc1b7 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLBackupBot.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -16,79 +17,72 @@ internal class SQLBackupBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _backupRecord.Id.ToString(); public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(SQLBackupBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public SQLBackupBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IServiceScopeFactory scopeFactory) { - this._databaseName = databaseName; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _databaseName = databaseName; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Backup of Db: {_databaseName}"); - EnsureFolderExists(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating backup of Db: {_databaseName}", _databaseName); + string directory = Path.GetDirectoryName(_backupRecord.Path); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); stopwatch.Start(); + Status = BotStatus.Running; //Execute Service bool backupedUp = false; - using (var scope = _scopeFactory.CreateScope()) + using (IServiceScope scope = _scopeFactory.CreateScope()) { IBackupProviderForSQLServer backupProviderService = scope.ServiceProvider.GetRequiredService(); backupedUp = await backupProviderService.BackupDatabaseAsync(_databaseName, _resourceGroup, _backupRecord); } stopwatch.Stop(); - if (backupedUp) - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.COMPLETED.ToString(), "Successfull", stopwatch.ElapsedMilliseconds); - else + if (!backupedUp) throw new Exception("Creating Backup Failed to Return Success Completion"); - _logger.LogInformation($"Creating Backup of Db: {_databaseName}...SUCCESS"); - } - catch (Exception ex) - { - this._logger.LogError(ex.Message); - stopwatch.Stop(); - UpdateBackupFeed(_backupRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void EnsureFolderExists(string path) - { - string directory = Path.GetDirectoryName(path); - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); - } - - private void UpdateBackupFeed(long recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } + DeliveryFeedType = DeliveryFeedType.BackupNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = null, + Status = BackupRecordStatus.COMPLETED, + Message = "Successfull", + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully Backup of Db: {_databaseName}", _databaseName); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + Status = BotStatus.Error; + _logger.LogError(ex.Message); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = null, + Status = BackupRecordStatus.ERROR, + Message = ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLRestoreBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLRestoreBot.cs deleted file mode 100644 index 33e9b88..0000000 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/SQLRestoreBot.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; -using SemanticBackup.Core.Models; -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; - -namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots -{ - internal class SQLRestoreBot : IBot - { - private readonly ResourceGroup _resourceGroup; - private readonly string _databaseName; - private readonly BackupRecord _backupRecord; - private readonly IServiceScopeFactory _scopeFactory; - private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _backupRecord.Id.ToString(); - public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; - public SQLRestoreBot(string databaseName, ResourceGroup resourceGroup, BackupRecord backupRecord, IServiceScopeFactory scopeFactory) - { - this._databaseName = databaseName; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; - //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); - } - public async Task RunAsync() - { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); - try - { - _logger.LogInformation($"Begining RESTORE of Db: {_databaseName}"); - EnsureBackupFileExists(_backupRecord.Path); - await Task.Delay(new Random().Next(1000)); - stopwatch.Start(); - //Execute Service - bool restoredSuccess = false; - using (var scope = _scopeFactory.CreateScope()) - { - IBackupProviderForSQLServer backupProviderService = scope.ServiceProvider.GetRequiredService(); - restoredSuccess = await backupProviderService.RestoreDatabaseAsync(_databaseName, _resourceGroup, _backupRecord); - } - stopwatch.Stop(); - if (restoredSuccess) - UpdateRestoreStatusFeed(_backupRecord.Id, BackupRecordRestoreStatus.RESTORE_COMPLETED.ToString(), "Restored Successfully"); - else - throw new Exception("RESTORE Failed to Return Success Completion"); - _logger.LogInformation($"RESTORE of Db: {_databaseName}...SUCCESS, Completion Time: {stopwatch.ElapsedMilliseconds:N0} Milliseconds"); - } - catch (Exception ex) - { - this._logger.LogError(ex.Message); - stopwatch.Stop(); - UpdateRestoreStatusFeed(_backupRecord.Id, BackupRecordRestoreStatus.FAILED_RESTORE.ToString(), ex.Message); - } - } - - private void EnsureBackupFileExists(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateRestoreStatusFeed(long recordId, string status, string message) - { - try - { - using (var scope = _scopeFactory.CreateScope()) - { - IBackupRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateRestoreStatusFeedAsync(recordId, status, message); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; - } - } - } -} diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs index 598f80e..def18a3 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderAzureStorageBot.cs @@ -1,11 +1,11 @@ using Azure.Storage.Blobs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -17,87 +17,81 @@ internal class UploaderAzureStorageBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderAzureStorageBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public UploaderAzureStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Uploading Backup File via Azure Blob Storage...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to AzureBlobStorage: {Path}", _backupRecord.Path); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); AzureBlobStorageDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.AzureBlobStorage ?? throw new Exception("no valid azure blob storage config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); - //FTP Upload + Status = BotStatus.Running; + //check path + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); + //proceed string executionMessage = "Azure Blob Storage Uploading..."; //Container - string validContainer = (string.IsNullOrWhiteSpace(settings.BlobContainer)) ? "backups" : settings.BlobContainer; + string validContainer = string.IsNullOrWhiteSpace(settings.BlobContainer) ? "backups" : settings.BlobContainer; //Filename string fileName = Path.GetFileName(this._backupRecord.Path); //Proceed if (string.IsNullOrWhiteSpace(settings.ConnectionString)) throw new Exception("Invalid Connection String"); //Proceed - using (FileStream stream = File.Open(this._backupRecord.Path, FileMode.Open)) + using (FileStream stream = File.Open(_backupRecord.Path, FileMode.Open)) { - BlobContainerClient containerClient = new BlobContainerClient(settings.ConnectionString, validContainer); + BlobContainerClient containerClient = new(settings.ConnectionString, validContainer); BlobClient blobClient = containerClient.GetBlobClient(fileName); - _ = await blobClient.UploadAsync(stream, true); - executionMessage = $"Uploaded Container: {validContainer}"; + _ = await blobClient.UploadAsync(stream, true, cancellationToken); + executionMessage = $"Uploaded to Container: {validContainer}"; } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Uploading Backup File via Azure Blob Storage: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to AzureBlobStorage: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs index 2f4c5e9..b944477 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderDropboxBot.cs @@ -7,6 +7,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -18,88 +19,96 @@ internal class UploaderDropboxBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderDropboxBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public UploaderDropboxBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Uploading Backup File via DropBox...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to Dropbox: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); DropboxDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Dropbox ?? throw new Exception("no valid dropbox config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); + Status = BotStatus.Running; + //check path + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); //FTP Upload string executionMessage = "Dropbox Uploading..."; //Directory - string validDirectory = (string.IsNullOrWhiteSpace(settings.Directory)) ? "/" : settings.Directory; - validDirectory = (validDirectory.EndsWith("/")) ? validDirectory : validDirectory + "/"; - validDirectory = (validDirectory.StartsWith("/")) ? validDirectory : "/" + validDirectory; + string validDirectory = string.IsNullOrWhiteSpace(settings.Directory) ? "/" : settings.Directory; + validDirectory = validDirectory.EndsWith('/') ? validDirectory : validDirectory + "/"; + validDirectory = validDirectory.StartsWith('/') ? validDirectory : "/" + validDirectory; //Filename string fileName = Path.GetFileName(this._backupRecord.Path); //Proceed if (string.IsNullOrWhiteSpace(settings.AccessToken)) throw new Exception("Access Token is NULL"); //Proceed - using (DropboxClient dbx = new DropboxClient(settings.AccessToken.Trim())) - using (var mem = new MemoryStream(await File.ReadAllBytesAsync(this._backupRecord.Path))) + using (DropboxClient dbx = new(settings.AccessToken.Trim())) + using (MemoryStream mem = new(await File.ReadAllBytesAsync(this._backupRecord.Path, cancellationToken))) { - var updated = await dbx.Files.UploadAsync(string.Format("{0}{1}", validDirectory, fileName), WriteMode.Overwrite.Instance, body: mem); + FileMetadata updated = await dbx.Files.UploadAsync(string.Format("{0}{1}", validDirectory, fileName), WriteMode.Overwrite.Instance, body: mem); executionMessage = $"Uploaded to: {validDirectory}"; } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Uploading Backup File via DropBox: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to Dropbox: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) { try { - using (var scope = _scopeFactory.CreateScope()) - { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } + using IServiceScope scope = _scopeFactory.CreateScope(); + IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); + _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); } catch (Exception ex) { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + _logger.LogError("Error Updating Feed: {Message}", ex.Message); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs index 98a003f..6a07506 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderEmailSMTPBot.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SemanticBackup.Core; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Collections.Generic; @@ -9,6 +8,7 @@ using System.IO; using System.Net.Mail; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -20,36 +20,36 @@ internal class UploaderEmailSMTPBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderEmailSMTPBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; public UploaderEmailSMTPBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Sending Email via SMTP...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to SMTP Mail: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); SmtpDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Smtp ?? throw new Exception("no valid smtp config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); + Status = BotStatus.Running; + //check folder + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); //Check any Recepient List emailRecipients = settings.GetValidSmtpDestinations(); if (emailRecipients.Count == 0) @@ -57,86 +57,76 @@ public async Task RunAsync() //proceed string fileName = Path.GetFileName(this._backupRecord.Path); string executionMessage = "Sending Email...."; - using (MailMessage e_mail = new MailMessage()) + using (MailMessage e_mail = new()) { - using (SmtpClient Smtp_Server = new SmtpClient()) - { - //Configs - Smtp_Server.UseDefaultCredentials = false; - Smtp_Server.Credentials = new System.Net.NetworkCredential(settings.SMTPEmailAddress, settings.SMTPEmailCredentials); - Smtp_Server.Port = settings.SMTPPort; //Use 587 - Smtp_Server.EnableSsl = settings.SMTPEnableSSL; - Smtp_Server.Host = settings.SMTPHost; - //Attachment - byte[] fileContents; - using (StreamReader sourceStream = new StreamReader(this._backupRecord.Path)) - fileContents = Encoding.UTF8.GetBytes(await sourceStream.ReadToEndAsync()); - MemoryStream strm = new MemoryStream(fileContents); - Attachment AttachData = new Attachment(strm, fileName); - e_mail.Attachments.Add(AttachData); - //Other Configs - e_mail.From = new MailAddress(settings.SMTPEmailAddress, settings.SMTPDefaultSMTPFromName); - //This Configs Should be Placed here - e_mail.Subject = $"DATABASE BACKUP | {fileName}"; - e_mail.IsBodyHtml = true; - e_mail.Body = $"Find Attached Database Backup Record:
File: {fileName}



Powered By Crudsoft Technologies
email: support@crudsofttechnologies.com
"; + using SmtpClient Smtp_Server = new(); + //Configs + Smtp_Server.UseDefaultCredentials = false; + Smtp_Server.Credentials = new System.Net.NetworkCredential(settings.SMTPEmailAddress, settings.SMTPEmailCredentials); + Smtp_Server.Port = settings.SMTPPort; + Smtp_Server.EnableSsl = settings.SMTPEnableSSL; + Smtp_Server.Host = settings.SMTPHost; + //Attachment + byte[] fileContents; + using (StreamReader sourceStream = new(this._backupRecord.Path)) + fileContents = Encoding.UTF8.GetBytes(await sourceStream.ReadToEndAsync(cancellationToken)); + MemoryStream strm = new(fileContents); + Attachment AttachData = new(strm, fileName); + e_mail.Attachments.Add(AttachData); + //Other Configs + e_mail.From = new MailAddress(settings.SMTPEmailAddress, settings.SMTPDefaultSMTPFromName); + //This Configs Should be Placed here + e_mail.Subject = $"DATABASE BACKUP | {fileName}"; + e_mail.IsBodyHtml = true; + e_mail.Body = $"Find Attached Database Backup Record:
File: {fileName}



Powered By Crudsoft Technologies
email: support@crudsofttechnologies.com
"; - //Add Default - e_mail.To.Add(emailRecipients[0]); + //Add Default + e_mail.To.Add(emailRecipients[0]); - if (emailRecipients.Count > 1) + if (emailRecipients.Count > 1) + { + int addIndex = 0; + foreach (string dest in emailRecipients) { - int addIndex = 0; - foreach (string dest in emailRecipients) - { - //Skip the First - if (addIndex != 0) - e_mail.Bcc.Add(dest); - addIndex++; - } + //Skip the First + if (addIndex != 0) + e_mail.Bcc.Add(dest); + addIndex++; } - - //Finally Send - await Smtp_Server.SendMailAsync(e_mail); - executionMessage = $"Sent to: {settings.SMTPDestinations}"; } + + //Finally Send + await Smtp_Server.SendMailAsync(e_mail, cancellationToken); + executionMessage = $"Sent to: {settings.SMTPDestinations}"; } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Sending Email via SMTP: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to SMTP Server: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs index 2d343b5..92e4c76 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderFTPBot.cs @@ -1,13 +1,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; using System.IO; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; +using System.Net; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -19,109 +17,119 @@ internal class UploaderFTPBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderFTPBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; public UploaderFTPBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Uploading Backup File via FTP...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("uploading file to FTP Server: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); FtpDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.Ftp ?? throw new Exception("no valid ftp config"); stopwatch.Start(); - //Upload FTP - CheckIfFileExistsOrRemove(this._backupRecord.Path); + Status = BotStatus.Running; + //check file + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); //FTP Upload string executionMessage = "FTP Uploading..."; //Directory string validDirectory = (string.IsNullOrWhiteSpace(settings.Directory)) ? "/" : settings.Directory; - validDirectory = (validDirectory.EndsWith("/")) ? validDirectory : validDirectory + "/"; - validDirectory = (validDirectory.StartsWith("/")) ? validDirectory : "/" + validDirectory; + validDirectory = validDirectory.EndsWith('/') ? validDirectory : validDirectory + "/"; + validDirectory = validDirectory.StartsWith('/') ? validDirectory : "/" + validDirectory; string validServerName = settings.Server.Replace("ftp", string.Empty).Replace("/", string.Empty).Replace(":", string.Empty); //Filename string fileName = Path.GetFileName(this._backupRecord.Path); //Proceed try { - string fullServerUrl = string.Format("ftp://{0}{1}{2}", validServerName, validDirectory, fileName); + string fullServerUrl = $"ftp://{validServerName}{validDirectory}{fileName}"; byte[] fileContents; using (FileStream sourceStream = File.OpenRead(this._backupRecord.Path)) { fileContents = new byte[sourceStream.Length]; - await sourceStream.ReadAsync(fileContents, 0, fileContents.Length); + await sourceStream.ReadAsync(fileContents, cancellationToken); } - using (HttpClient client = new HttpClient()) + +#pragma warning disable SYSLIB0014 // Type or member is obsolete + FtpWebRequest request = (FtpWebRequest)WebRequest.Create(fullServerUrl); +#pragma warning restore SYSLIB0014 // Type or member is obsolete + request.Method = WebRequestMethods.Ftp.UploadFile; + request.Credentials = new NetworkCredential(settings.Username, settings.Password); + request.EnableSsl = false; // Set to true if your FTP server uses FTPS + request.UsePassive = true; + request.UseBinary = true; + request.KeepAlive = false; + request.ContentLength = fileContents.Length; + + // Write to the request stream + using (Stream requestStream = await request.GetRequestStreamAsync()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{settings.Username}:{settings.Password}"))); - ByteArrayContent content = new ByteArrayContent(fileContents); - HttpResponseMessage response = await client.PutAsync(fullServerUrl, content); - if (response.IsSuccessStatusCode) + await requestStream.WriteAsync(fileContents, 0, fileContents.Length, cancellationToken); + } + + // Get the response to ensure upload completed + using (FtpWebResponse response = (FtpWebResponse)await request.GetResponseAsync()) + { + if (response.StatusCode == FtpStatusCode.ClosingData) { executionMessage = $"Uploaded to Server: {settings.Server}"; } else { - throw new Exception($"Failed to upload. Status code: {response.StatusCode}"); + throw new Exception($"Failed to upload. FTP status: {response.StatusDescription}"); } } } catch (Exception ex) { - throw new Exception(ex.Message); + throw new Exception($"Upload failed: {ex.Message}"); } stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), executionMessage, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Uploading Backup File via FTP: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + + _logger.LogInformation("Successfully uploaded file to FTP Server: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), (ex.InnerException != null) ? $"Error Uploading: {ex.InnerException.Message}" : ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void CheckIfFileExistsOrRemove(string path) - { - if (!File.Exists(path)) - throw new Exception($"No Database File In Path or May have been deleted, Path: {path}"); - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs index 408b7e0..09a6127 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderLinkGenBot.cs @@ -2,10 +2,10 @@ using Microsoft.Extensions.Logging; using SemanticBackup.Core; using SemanticBackup.Core.Extensions; -using SemanticBackup.Core.Interfaces; using SemanticBackup.Core.Models; using System; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots @@ -17,67 +17,64 @@ internal class UploaderLinkGenBot : IBot private readonly BackupRecord _backupRecord; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; - public bool IsCompleted { get; private set; } = false; - public bool IsStarted { get; private set; } = false; - - public string ResourceGroupId => _resourceGroup.Id; - public string BotId => _contentDeliveryRecord.Id; public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderLinkGenBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + public UploaderLinkGenBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) { - this._contentDeliveryRecord = contentDeliveryRecord; - this._resourceGroup = resourceGroup; - this._backupRecord = backupRecord; - this._scopeFactory = scopeFactory; + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; //Logger - using (var scope = _scopeFactory.CreateScope()) - _logger = scope.ServiceProvider.GetRequiredService>(); + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); } - public async Task RunAsync() + + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) { - this.IsStarted = true; - this.IsCompleted = false; - Stopwatch stopwatch = new Stopwatch(); + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); try { - _logger.LogInformation($"Creating Download Link...."); - await Task.Delay(new Random().Next(1000)); + _logger.LogInformation("creating download link for file: {Path}", _backupRecord.Path); + await Task.Delay(Random.Shared.Next(1000), cancellationToken); DownloadLinkDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.DownloadLink ?? throw new Exception("no valid download link config"); stopwatch.Start(); + //get download link:: string contentLink = 5.GenerateUniqueId(); if (settings.DownloadLinkType == "LONG") contentLink = string.Format("{0}?token={1}", 55.GenerateUniqueId(), $"{this._backupRecord.Id}|{this._resourceGroup.Id}".ToMD5String()); - //Job to Do - await Task.Delay(new Random().Next(3000)); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordDeliveryStatus.READY.ToString(), contentLink, stopwatch.ElapsedMilliseconds); - _logger.LogInformation($"Creating Download Link: {_backupRecord.Path}... SUCCESS"); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = contentLink, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully created download link for file: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; } catch (Exception ex) { - this._logger.LogError(ex.Message); + Status = BotStatus.Error; + _logger.LogError(ex.Message); stopwatch.Stop(); - UpdateBackupFeed(_contentDeliveryRecord.Id, BackupRecordBackupStatus.ERROR.ToString(), ex.Message, stopwatch.ElapsedMilliseconds); - } - } - - private void UpdateBackupFeed(string recordId, string status, string message, long elapsed) - { - try - { - using (var scope = _scopeFactory.CreateScope()) + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed { - IContentDeliveryRecordRepository _persistanceService = scope.ServiceProvider.GetRequiredService(); - _persistanceService.UpdateStatusFeedAsync(recordId, status, message, elapsed); - } - } - catch (Exception ex) - { - this._logger.LogError("Error Updating Feed: " + ex.Message); - } - finally - { - IsCompleted = true; + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs new file mode 100644 index 0000000..d5994eb --- /dev/null +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/UploaderObjectStorageBot.cs @@ -0,0 +1,108 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Minio; +using Minio.DataModel.Args; +using Minio.DataModel.Response; +using SemanticBackup.Core.Models; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots +{ + internal class UploaderObjectStorageBot : IBot + { + private readonly BackupRecordDelivery _contentDeliveryRecord; + private readonly ResourceGroup _resourceGroup; + private readonly BackupRecord _backupRecord; + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow; + public string BotId => $"{_resourceGroup.Id}::{_backupRecord.Id}::{nameof(UploaderObjectStorageBot)}"; + public string ResourceGroupId => _resourceGroup.Id; + public BotStatus Status { get; internal set; } = BotStatus.NotReady; + + public UploaderObjectStorageBot(ResourceGroup resourceGroup, BackupRecord backupRecord, BackupRecordDelivery contentDeliveryRecord, IServiceScopeFactory scopeFactory) + { + _contentDeliveryRecord = contentDeliveryRecord; + _resourceGroup = resourceGroup; + _backupRecord = backupRecord; + _scopeFactory = scopeFactory; + //Logger + using IServiceScope scope = _scopeFactory.CreateScope(); + _logger = scope.ServiceProvider.GetRequiredService>(); + } + public async Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken) + { + Status = BotStatus.Starting; + Stopwatch stopwatch = new(); + try + { + _logger.LogInformation("uploading file to ObjectStorage: {Path}", _backupRecord.Path); + //proceed + await Task.Delay(Random.Shared.Next(1000), cancellationToken); + ObjectStorageDeliveryConfig settings = _resourceGroup.BackupDeliveryConfig.ObjectStorage ?? throw new Exception("no valid object storage config"); + stopwatch.Start(); + Status = BotStatus.Running; + //check path + if (!File.Exists(_backupRecord.Path)) + throw new Exception($"No Database File In Path or May have been deleted, Path: {_backupRecord.Path}"); + //proceed + string executionMessage = "Object Storage Uploading..."; + //Container + string validBucket = string.IsNullOrWhiteSpace(settings.Bucket) ? "backups" : settings.Bucket; + //Filename + string fileName = Path.GetFileName(this._backupRecord.Path); + //Proceed + using IMinioClient minioClient = new MinioClient().WithEndpoint(settings.Server, settings.Port).WithCredentials(settings.AccessKey, settings.SecretKey).WithSSL(settings.UseSsl).Build(); + using (FileStream stream = File.Open(_backupRecord.Path, FileMode.Open)) + { + //upload object + PutObjectResponse putResponse = await minioClient.PutObjectAsync(new PutObjectArgs() + .WithBucket(settings.Bucket) + .WithObject(fileName) + .WithStreamData(stream) + .WithObjectSize(stream.Length), cancellationToken); + //proceed + if (putResponse.ResponseStatusCode != System.Net.HttpStatusCode.OK) + { + executionMessage = $"Failed: {putResponse.ResponseStatusCode}"; + throw new Exception(executionMessage); + } + //proceed + executionMessage = $"Uploaded to Bucket: {validBucket}"; + } + stopwatch.Stop(); + //notify update + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.READY, + Message = executionMessage, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + _logger.LogInformation("Successfully uploaded file to ObjectStorage: {Path}", _backupRecord.Path); + Status = BotStatus.Completed; + } + catch (Exception ex) + { + Status = BotStatus.Error; + _logger.LogError(ex.Message); + stopwatch.Stop(); + await onDeliveryFeedUpdate(new BackupRecordDeliveryFeed + { + DeliveryFeedType = DeliveryFeedType.BackupDeliveryNotify, + BackupRecordId = _backupRecord.Id, + BackupRecordDeliveryId = _contentDeliveryRecord.Id, + Status = BackupRecordStatus.ERROR, + Message = (ex.InnerException != null) ? $"Error: {ex.InnerException.Message}" : ex.Message, + ElapsedMilliseconds = stopwatch.ElapsedMilliseconds + }, cancellationToken); + } + } + } +} diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs index 8fd53a3..f7e01d7 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/Bots/_IBot.cs @@ -1,15 +1,42 @@ -using System; +using SemanticBackup.Core.Models; +using System; +using System.Threading; using System.Threading.Tasks; namespace SemanticBackup.Infrastructure.BackgroundJobs.Bots { public interface IBot { - Task RunAsync(); - bool IsCompleted { get; } - bool IsStarted { get; } - string ResourceGroupId { get; } string BotId { get; } + string ResourceGroupId { get; } DateTime DateCreatedUtc { get; } + Task RunAsync(Func onDeliveryFeedUpdate, CancellationToken cancellationToken); + BotStatus Status { get; } + } + + public class BackupRecordDeliveryFeed + { + public DeliveryFeedType DeliveryFeedType { get; set; } = DeliveryFeedType.BackupDeliveryNotify; + public long BackupRecordId { get; set; } = 0; + public string BackupRecordDeliveryId { get; set; } + public BackupRecordStatus Status { get; set; } + public string Message { get; set; } + public string NewFilePath { get; set; } = null; + public long ElapsedMilliseconds { get; set; } = 0; + } + + public enum DeliveryFeedType + { + BackupNotify, + BackupDeliveryNotify, + } + + public enum BotStatus + { + NotReady, + Starting, + Running, + Completed, + Error } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs index d309351..83176d9 100644 --- a/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs +++ b/SemanticBackup.Infrastructure/BackgroundJobs/BotsManagerBackgroundJob.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using SemanticBackup.Core.Interfaces; using SemanticBackup.Infrastructure.BackgroundJobs.Bots; using System; using System.Collections.Generic; @@ -12,16 +14,18 @@ namespace SemanticBackup.Infrastructure.BackgroundJobs public class BotsManagerBackgroundJob : IHostedService { private readonly ILogger _logger; - private List Bots { get; set; } = new List(); + private readonly IServiceScopeFactory _serviceScopeFactory; - public BotsManagerBackgroundJob(ILogger logger) + private List Bots { get; set; } = []; + + public BotsManagerBackgroundJob(ILogger logger, IServiceScopeFactory serviceScopeFactory) { - this._logger = logger; + _logger = logger; + _serviceScopeFactory = serviceScopeFactory; } public Task StartAsync(CancellationToken cancellationToken) { - Bots = new List(); SetupBotsBackgroundService(cancellationToken); return Task.CompletedTask; } @@ -35,48 +39,75 @@ public Task StopAsync(CancellationToken cancellationToken) public void AddBot(List bots) => Bots.AddRange(bots); public void TerminateBots(List botIds) { - if (botIds != null && botIds.Count > 0) + if (botIds.Count != 0) { - List botsToRemove = this.Bots.Where(x => botIds.Contains(x.BotId)).ToList(); + List botsToRemove = [.. Bots.Where(x => botIds.Contains(x.BotId))]; foreach (IBot bot in botsToRemove) - this.Bots.Remove(bot); + Bots.Remove(bot); } } + public bool HasAvailableResourceGroupBotsCount(string resourceGroupId, int maximumThreads = 1) { - int resourceBots = this.Bots.Where(x => x.ResourceGroupId == resourceGroupId).Count(); - int runningResourceGrpThreads = resourceBots; - int availableResourceGrpThreads = maximumThreads - runningResourceGrpThreads; - return availableResourceGrpThreads > 0; + return Bots.Count(x => x.ResourceGroupId == resourceGroupId) < maximumThreads; } + private void SetupBotsBackgroundService(CancellationToken cancellationToken) { - var t = new Thread(async () => + Thread t = new(async () => { while (!cancellationToken.IsCancellationRequested) { try { - if (this.Bots != null && this.Bots.Count > 0) + if (Bots.Count > 0) { - //Start and Stop Bacup Bots - List botsNotStarted = this.Bots.Where(x => !x.IsStarted).OrderBy(x => x.DateCreatedUtc).ToList(); - if (botsNotStarted != null && botsNotStarted.Count > 0) - foreach (IBot bot in botsNotStarted) - _ = bot.RunAsync(); - //Remove Completed - List botsCompleted = this.Bots.Where(x => x.IsCompleted).ToList(); - if (botsCompleted != null && botsCompleted.Count > 0) - foreach (IBot bot in botsCompleted) - this.Bots.Remove(bot); + //Start not ready bots + List botsNotReady = (Bots.Where(x => x.Status == BotStatus.NotReady).OrderBy(x => x.DateCreatedUtc).ToList()) ?? []; + foreach (IBot bot in botsNotReady) + { + _ = bot.RunAsync(OnDeliveryFeedUpdate, cancellationToken); + } + //Remove Completed or Error + List botsCompleted = (Bots.Where(x => x.Status == BotStatus.Completed || x.Status == BotStatus.Error).ToList()) ?? []; + foreach (IBot bot in botsCompleted) + this.Bots.Remove(bot); } } - catch (Exception ex) { _logger.LogWarning($"Running Unstarted and Removing Completed Bots Failed: {ex.Message}"); } + catch (Exception ex) + { + _logger.LogWarning("Error: {Message}", ex.Message); + } //Delay - await Task.Delay(5000); + await Task.Delay(3000, cancellationToken); } }); t.Start(); } + + private async Task OnDeliveryFeedUpdate(BackupRecordDeliveryFeed feed, CancellationToken token) + { + try + { + if (feed.DeliveryFeedType == DeliveryFeedType.BackupNotify && feed.BackupRecordId > 0) + { + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IBackupRecordRepository _backupRecordRepo = scope.ServiceProvider.GetRequiredService(); + await _backupRecordRepo.UpdateStatusFeedAsync(feed.BackupRecordId, feed.Status.ToString(), feed.Message, feed.ElapsedMilliseconds, feed.NewFilePath); + } + else if (feed.DeliveryFeedType == DeliveryFeedType.BackupDeliveryNotify && !string.IsNullOrWhiteSpace(feed.BackupRecordDeliveryId)) + { + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IContentDeliveryRecordRepository _contentDeliveryRepo = scope.ServiceProvider.GetRequiredService(); + await _contentDeliveryRepo.UpdateStatusFeedAsync(feed.BackupRecordDeliveryId, feed.Status.ToString(), feed.Message, feed.ElapsedMilliseconds); + } + else + throw new Exception($"unsupported delivery-feed-type: {feed.DeliveryFeedType}"); + } + catch (Exception ex) + { + _logger.LogError("Error Updating Status Feed: {Message}", ex.Message); + } + } } } diff --git a/SemanticBackup.Infrastructure/BackgroundJobs/RestoreBackgroundJob.cs b/SemanticBackup.Infrastructure/BackgroundJobs/RestoreBackgroundJob.cs deleted file mode 100644 index 9fc6962..0000000 --- a/SemanticBackup.Infrastructure/BackgroundJobs/RestoreBackgroundJob.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using SemanticBackup.Core.Interfaces; -using SemanticBackup.Core.Models; -using SemanticBackup.Infrastructure.BackgroundJobs.Bots; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace SemanticBackup.Infrastructure.BackgroundJobs -{ - public class RestoreBackgroundJob : IHostedService - { - private readonly ILogger _logger; - private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly BotsManagerBackgroundJob _botsManagerBackgroundJob; - - public RestoreBackgroundJob( - ILogger logger, - IServiceScopeFactory serviceScopeFactory, - BotsManagerBackgroundJob botsManagerBackgroundJob) - { - this._logger = logger; - this._serviceScopeFactory = serviceScopeFactory; - this._botsManagerBackgroundJob = botsManagerBackgroundJob; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _logger.LogInformation("Starting service...."); - SetupBackgroundService(cancellationToken); - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - private void SetupBackgroundService(CancellationToken cancellationToken) - { - var t = new Thread(async () => - { - while (!cancellationToken.IsCancellationRequested) - { - await Task.Delay(5000); - try - { - using (var scope = _serviceScopeFactory.CreateScope()) - { - //DI INJECTIONS - IBackupRecordRepository backupRecordPersistanceService = scope.ServiceProvider.GetRequiredService(); - IResourceGroupRepository resourceGroupPersistanceService = scope.ServiceProvider.GetRequiredService(); - IDatabaseInfoRepository databaseInfoPersistanceService = scope.ServiceProvider.GetRequiredService(); - //Proceed - List backupRestores = await backupRecordPersistanceService.GetAllByRestoreStatusAsync(BackupRecordRestoreStatus.PENDING_RESTORE.ToString()); - if (backupRestores != null && backupRestores.Count > 0) - { - foreach (BackupRecord backupRecord in backupRestores.OrderBy(x => x.RegisteredDateUTC)) - { - _logger.LogInformation($"Processing Queued Backup RESTORE Record Key: #{backupRecord.Id}..."); - BackupDatabaseInfo backupDatabaseInfo = await databaseInfoPersistanceService.GetByIdAsync(backupRecord.BackupDatabaseInfoId); - ResourceGroup resourceGroup = await resourceGroupPersistanceService.GetByIdOrKeyAsync(backupDatabaseInfo.ResourceGroupId); - if (backupDatabaseInfo != null && resourceGroup != null) - { - if (_botsManagerBackgroundJob.HasAvailableResourceGroupBotsCount(resourceGroup.Id, resourceGroup.MaximumRunningBots)) - { - if (resourceGroup.DbType.Contains("SQLSERVER")) - _botsManagerBackgroundJob.AddBot(new SQLRestoreBot(backupDatabaseInfo.DatabaseName, resourceGroup, backupRecord, _serviceScopeFactory)); - else if (resourceGroup.DbType.Contains("MYSQL") || resourceGroup.DbType.Contains("MARIADB")) - throw new Exception("No RESTORE Bot for MYSQL"); - else - throw new Exception($"No Bot is registered to Handle Database RESTORE of Type: {resourceGroup.DbType}"); - //Finally Update Status - bool updated = await backupRecordPersistanceService.UpdateRestoreStatusFeedAsync(backupRecord.Id, BackupRecordRestoreStatus.EXECUTING_RESTORE.ToString(), "Executing Restore...."); - if (updated) - _logger.LogInformation($"Processing Queued Backup RESTORE Record Key: #{backupRecord.Id}...SUCCESS"); - else - _logger.LogWarning($"Queued for Backup RESTORE but was unable to update backup record Key: #{backupRecord.Id} status"); - } - else - _logger.LogInformation($"Resource Group With Id: {resourceGroup.Id} has Exceeded its Maximum Allocated Running Threads Count: {resourceGroup.MaximumRunningBots}"); - } - } - } - } - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - - } - }); - t.Start(); - } - } -} diff --git a/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs index d2bead5..3d44ae8 100644 --- a/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/BackupRecordRepositoryLiteDb.cs @@ -193,7 +193,7 @@ public async Task UpdateAsync(BackupRecord record) public async Task> GetAllReadyAndPendingDeliveryAsync() { - return await _db.GetCollection().Query().Where(x => !x.ExecutedDeliveryRun && x.BackupStatus == BackupRecordBackupStatus.READY.ToString()).OrderBy(x => x.Id).ToListAsync(); + return await _db.GetCollection().Query().Where(x => !x.ExecutedDeliveryRun && x.BackupStatus == BackupRecordStatus.READY.ToString()).OrderBy(x => x.Id).ToListAsync(); } public async Task UpdateDeliveryRunnedAsync(long backupRecordId, bool hasRun, string executedDeliveryRunStatus) { diff --git a/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs index c7f28cf..266cc6b 100644 --- a/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/ContentDeliveryRecordRepositoryLiteDb.cs @@ -35,8 +35,8 @@ public async Task> GetAllByBackupRecordIdAsync(long i public async Task AddOrUpdateAsync(BackupRecordDelivery record) { - var collection = _db.GetCollection(); - var objFound = await collection.Query().Where(x => x.Id == record.Id).FirstOrDefaultAsync(); + ILiteCollectionAsync collection = _db.GetCollection(); + BackupRecordDelivery objFound = await collection.Query().Where(x => x.Id == record.Id).FirstOrDefaultAsync(); if (objFound != null) { objFound.StatusUpdateDateUTC = record.StatusUpdateDateUTC; @@ -58,8 +58,8 @@ public async Task AddOrUpdateAsync(BackupRecordDelivery record) public async Task UpdateStatusFeedAsync(string id, string status, string message = null, long executionInMilliseconds = 0) { - var collection = _db.GetCollection(); - var objFound = await collection.Query().Where(x => x.Id == id).FirstOrDefaultAsync(); + ILiteCollectionAsync collection = _db.GetCollection(); + BackupRecordDelivery objFound = await collection.Query().Where(x => x.Id == id).FirstOrDefaultAsync(); if (objFound != null) { objFound.CurrentStatus = status; @@ -98,7 +98,7 @@ public async Task> GetAllNoneResponsiveAsync(List statusChe private void DispatchUpdatedStatus(BackupRecordDelivery record, bool isNewRecord = false) { if (_backupRecordStatusChangedNotifiers != null) - foreach (var notifier in _backupRecordStatusChangedNotifiers) + foreach (IRecordStatusChangedNotifier notifier in _backupRecordStatusChangedNotifiers) try { notifier.DispatchContentDeliveryUpdatedStatus(record, isNewRecord); diff --git a/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs b/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs index 09c670f..40c5c70 100644 --- a/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs +++ b/SemanticBackup.Infrastructure/Implementations/ResourceGroupRepositoryLiteDb.cs @@ -10,7 +10,7 @@ namespace SemanticBackup.Infrastructure.Implementations { public class ResourceGroupRepositoryLiteDb : IResourceGroupRepository { - private LiteDatabaseAsync _db; + private readonly LiteDatabaseAsync _db; private readonly IBackupRecordRepository _backupRecordPersistanceService; private readonly IBackupScheduleRepository _backupSchedulePersistanceService; private readonly IDatabaseInfoRepository _databaseInfoPersistanceService; @@ -50,14 +50,13 @@ public async Task VerifyByIdOrKeyThrowIfNotExistAsync(string reso return await GetByIdOrKeyAsync(resourceGroupIdentifier) ?? throw new Exception($"resource group not found with provided identity: {resourceGroupIdentifier}"); } - public async Task RemoveAsync(string id) + public async Task RemoveAsync(string resourceGroupIdentifier) { - var collection = _db.GetCollection(); - var objFound = await collection.Query().Where(x => x.Id == id).FirstOrDefaultAsync(); + ResourceGroup objFound = await GetByIdOrKeyAsync(resourceGroupIdentifier); if (objFound != null) { - bool success = await collection.DeleteAsync(new BsonValue(objFound.Id)); - await TryDeleteAllResourcesForGroupAsync(id); + bool success = await _db.GetCollection().DeleteAsync(new BsonValue(objFound.Id)); + await TryDeleteAllResourcesForGroupAsync(objFound.Id); return success; } return false; diff --git a/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj b/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj index 34d6577..94c4055 100644 --- a/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj +++ b/SemanticBackup.Infrastructure/SemanticBackup.Infrastructure.csproj @@ -8,8 +8,9 @@ - + + @@ -18,7 +19,7 @@ - + diff --git a/SemanticBackup/Pages/ResourceGroups/Create.cshtml b/SemanticBackup/Pages/ResourceGroups/Create.cshtml index 31639fa..45e378a 100644 --- a/SemanticBackup/Pages/ResourceGroups/Create.cshtml +++ b/SemanticBackup/Pages/ResourceGroups/Create.cshtml @@ -51,42 +51,42 @@

Database Configuration

-
- -
-
- - -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
@@ -111,11 +111,12 @@
@@ -196,6 +197,47 @@ + + +
+ + + + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + + + + +
+
@@ -217,7 +259,7 @@
- +
@@ -230,6 +272,7 @@
+
@@ -269,7 +312,7 @@ -@section Scripts{ +@section Scripts {