public async Task <IOperationResult> Execute(Action <IOperationProgress> onProgress) { var databaseName = RestoreFromConfiguration.DatabaseName; var result = new RestoreResult { DataDirectory = RestoreFromConfiguration.DataDirectory }; try { var filesToRestore = await GetOrderedFilesToRestore(); using (_serverStore.ContextPool.AllocateOperationContext(out JsonOperationContext serverContext)) { if (onProgress == null) { onProgress = _ => { } } ; Stopwatch sw = null; RestoreSettings restoreSettings = null; var firstFile = filesToRestore[0]; var extension = Path.GetExtension(firstFile); var snapshotRestore = false; if ((extension == Constants.Documents.PeriodicBackup.SnapshotExtension) || (extension == Constants.Documents.PeriodicBackup.EncryptedSnapshotExtension)) { onProgress.Invoke(result.Progress); snapshotRestore = true; sw = Stopwatch.StartNew(); if (extension == Constants.Documents.PeriodicBackup.EncryptedSnapshotExtension) { _hasEncryptionKey = RestoreFromConfiguration.EncryptionKey != null || RestoreFromConfiguration.BackupEncryptionSettings?.Key != null; } // restore the snapshot restoreSettings = await SnapshotRestore(serverContext, firstFile, onProgress, result); if (restoreSettings != null && RestoreFromConfiguration.SkipIndexes) { // remove all indexes from the database record restoreSettings.DatabaseRecord.AutoIndexes = null; restoreSettings.DatabaseRecord.Indexes = null; } // removing the snapshot from the list of files filesToRestore.RemoveAt(0); } else { result.SnapshotRestore.Skipped = true; result.SnapshotRestore.Processed = true; onProgress.Invoke(result.Progress); } if (restoreSettings == null) { restoreSettings = new RestoreSettings { DatabaseRecord = new DatabaseRecord(databaseName) { // we only have a smuggler restore // use the encryption key to encrypt the database Encrypted = _hasEncryptionKey } }; DatabaseHelper.Validate(databaseName, restoreSettings.DatabaseRecord, _serverStore.Configuration); } var databaseRecord = restoreSettings.DatabaseRecord; if (databaseRecord.Settings == null) { databaseRecord.Settings = new Dictionary <string, string>(); } var runInMemoryConfigurationKey = RavenConfiguration.GetKey(x => x.Core.RunInMemory); databaseRecord.Settings.Remove(runInMemoryConfigurationKey); if (_serverStore.Configuration.Core.RunInMemory) { databaseRecord.Settings[runInMemoryConfigurationKey] = "false"; } var dataDirectoryConfigurationKey = RavenConfiguration.GetKey(x => x.Core.DataDirectory); databaseRecord.Settings.Remove(dataDirectoryConfigurationKey); // removing because we want to restore to given location, not to serialized in backup one if (_restoringToDefaultDataDirectory == false) { databaseRecord.Settings[dataDirectoryConfigurationKey] = RestoreFromConfiguration.DataDirectory; } if (_hasEncryptionKey) { // save the encryption key so we'll be able to access the database _serverStore.PutSecretKey(RestoreFromConfiguration.EncryptionKey, databaseName, overwrite: false); } var addToInitLog = new Action <string>(txt => // init log is not save in mem during RestoreBackup { var msg = $"[RestoreBackup] {DateTime.UtcNow} :: Database '{databaseName}' : {txt}"; if (Logger.IsInfoEnabled) { Logger.Info(msg); } }); var configuration = _serverStore .DatabasesLandlord .CreateDatabaseConfiguration(databaseName, ignoreDisabledDatabase: true, ignoreBeenDeleted: true, ignoreNotRelevant: true, databaseRecord); using (var database = new DocumentDatabase(databaseName, configuration, _serverStore, addToInitLog)) { // smuggler needs an existing document database to operate var options = InitializeOptions.SkipLoadingDatabaseRecord; if (snapshotRestore) { options |= InitializeOptions.GenerateNewDatabaseId; } database.Initialize(options); databaseRecord.Topology = new DatabaseTopology(); // restoring to the current node only databaseRecord.Topology.Members.Add(_nodeTag); // we are currently restoring, shouldn't try to access it databaseRecord.DatabaseState = DatabaseStateStatus.RestoreInProgress; var(index, _) = await _serverStore.WriteDatabaseRecordAsync(databaseName, databaseRecord, null, RaftIdGenerator.NewId(), restoreSettings.DatabaseValues, isRestore : true); await _serverStore.Cluster.WaitForIndexNotification(index); using (database.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext context)) { if (snapshotRestore) { await RestoreFromSmugglerFile(onProgress, database, firstFile, context); await SmugglerRestore(database, filesToRestore, context, databaseRecord, onProgress, result); result.SnapshotRestore.Processed = true; var summary = database.GetDatabaseSummary(); result.Documents.ReadCount += summary.DocumentsCount; result.Documents.Attachments.ReadCount += summary.AttachmentsCount; result.Counters.ReadCount += summary.CounterEntriesCount; result.RevisionDocuments.ReadCount += summary.RevisionsCount; result.Conflicts.ReadCount += summary.ConflictsCount; result.Indexes.ReadCount += databaseRecord.GetIndexesCount(); result.CompareExchange.ReadCount += summary.CompareExchangeCount; result.CompareExchangeTombstones.ReadCount += summary.CompareExchangeTombstonesCount; result.Identities.ReadCount += summary.IdentitiesCount; result.AddInfo($"Successfully restored {result.SnapshotRestore.ReadCount} files during snapshot restore, took: {sw.ElapsedMilliseconds:#,#;;0}ms"); onProgress.Invoke(result.Progress); } else { await SmugglerRestore(database, filesToRestore, context, databaseRecord, onProgress, result); } DisableOngoingTasksIfNeeded(databaseRecord); result.DatabaseRecord.Processed = true; result.Documents.Processed = true; result.RevisionDocuments.Processed = true; result.Conflicts.Processed = true; result.Indexes.Processed = true; result.Counters.Processed = true; result.Identities.Processed = true; result.CompareExchange.Processed = true; result.Subscriptions.Processed = true; onProgress.Invoke(result.Progress); } } // after the db for restore is done, we can safely set the db state to normal and write the DatabaseRecord databaseRecord.DatabaseState = DatabaseStateStatus.Normal; var(updateIndex, _) = await _serverStore.WriteDatabaseRecordAsync(databaseName, databaseRecord, null, RaftIdGenerator.DontCareId, isRestore : true); await _serverStore.Cluster.WaitForIndexNotification(updateIndex); if (databaseRecord.Topology.RelevantFor(_serverStore.NodeTag)) { // we need to wait for the database record change to be propagated properly var db = await _serverStore.DatabasesLandlord.TryGetOrCreateResourceStore(databaseName); await db.RachisLogIndexNotifications.WaitForIndexNotification(updateIndex, _operationCancelToken.Token); } return(result); } } catch (Exception e) { if (Logger.IsOperationsEnabled) { Logger.Operations("Failed to restore database", e); } var alert = AlertRaised.Create( RestoreFromConfiguration.DatabaseName, "Failed to restore database", $"Could not restore database named {RestoreFromConfiguration.DatabaseName}", AlertType.RestoreError, NotificationSeverity.Error, details: new ExceptionDetails(e)); _serverStore.NotificationCenter.Add(alert); using (_serverStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context)) { bool databaseExists; using (context.OpenReadTransaction()) { databaseExists = _serverStore.Cluster.DatabaseExists(context, RestoreFromConfiguration.DatabaseName); } if (databaseExists == false) { // delete any files that we already created during the restore IOExtensions.DeleteDirectory(RestoreFromConfiguration.DataDirectory); } else { var deleteResult = await _serverStore.DeleteDatabaseAsync(RestoreFromConfiguration.DatabaseName, true, new[] { _serverStore.NodeTag }, RaftIdGenerator.DontCareId); await _serverStore.Cluster.WaitForIndexNotification(deleteResult.Index); } } result.AddError($"Error occurred during restore of database {databaseName}. Exception: {e.Message}"); onProgress.Invoke(result.Progress); throw; } finally { Dispose(); } }
public async Task <IOperationResult> Execute(Action <IOperationProgress> onProgress) { var databaseName = _restoreConfiguration.DatabaseName; var result = new RestoreResult { DataDirectory = _restoreConfiguration.DataDirectory }; try { if (onProgress == null) { onProgress = _ => { } } ; Stopwatch sw = null; RestoreSettings restoreSettings = null; var firstFile = _filesToRestore[0]; var lastFile = _filesToRestore.Last(); var extension = Path.GetExtension(firstFile); var snapshotRestore = false; if (extension == Constants.Documents.PeriodicBackup.SnapshotExtension) { onProgress.Invoke(result.Progress); snapshotRestore = true; sw = Stopwatch.StartNew(); // restore the snapshot restoreSettings = SnapshotRestore(firstFile, _restoreConfiguration.DataDirectory, onProgress, result); // removing the snapshot from the list of files _filesToRestore.RemoveAt(0); } else { result.SnapshotRestore.Skipped = true; result.SnapshotRestore.Processed = true; onProgress.Invoke(result.Progress); } if (restoreSettings == null) { restoreSettings = new RestoreSettings { DatabaseRecord = new DatabaseRecord(databaseName) { // we only have a smuggler restore // use the encryption key to encrypt the database Encrypted = _hasEncryptionKey } }; DatabaseHelper.Validate(databaseName, restoreSettings.DatabaseRecord, _serverStore.Configuration); } var databaseRecord = restoreSettings.DatabaseRecord; if (databaseRecord.Settings == null) { databaseRecord.Settings = new Dictionary <string, string>(); } var runInMemoryConfigurationKey = RavenConfiguration.GetKey(x => x.Core.RunInMemory); databaseRecord.Settings.Remove(runInMemoryConfigurationKey); if (_serverStore.Configuration.Core.RunInMemory) { databaseRecord.Settings[runInMemoryConfigurationKey] = "false"; } var dataDirectoryConfigurationKey = RavenConfiguration.GetKey(x => x.Core.DataDirectory); databaseRecord.Settings.Remove(dataDirectoryConfigurationKey); // removing because we want to restore to given location, not to serialized in backup one if (_restoringToDefaultDataDirectory == false) { databaseRecord.Settings[dataDirectoryConfigurationKey] = _restoreConfiguration.DataDirectory; } if (_hasEncryptionKey) { // save the encryption key so we'll be able to access the database _serverStore.PutSecretKey(_restoreConfiguration.EncryptionKey, databaseName, overwrite: false); } var addToInitLog = new Action <string>(txt => // init log is not save in mem during RestoreBackup { var msg = $"[RestoreBackup] {DateTime.UtcNow} :: Database '{databaseName}' : {txt}"; if (Logger.IsInfoEnabled) { Logger.Info(msg); } }); using (var database = new DocumentDatabase(databaseName, new RavenConfiguration(databaseName, ResourceType.Database) { Core = { DataDirectory = new PathSetting(_restoreConfiguration.DataDirectory), RunInMemory = false } }, _serverStore, addToInitLog)) { // smuggler needs an existing document database to operate var options = InitializeOptions.SkipLoadingDatabaseRecord; if (snapshotRestore) { options |= InitializeOptions.GenerateNewDatabaseId; } database.Initialize(options); if (snapshotRestore) { result.SnapshotRestore.Processed = true; var summary = database.GetDatabaseSummary(); result.Documents.ReadCount += summary.DocumentsCount; result.Documents.Attachments.ReadCount += summary.AttachmentsCount; result.Counters.ReadCount += summary.CountersCount; result.RevisionDocuments.ReadCount += summary.RevisionsCount; result.Conflicts.ReadCount += summary.ConflictsCount; result.Indexes.ReadCount += databaseRecord.GetIndexesCount(); result.AddInfo($"Successfully restored {result.SnapshotRestore.ReadCount} " + $"files during snapshot restore, took: {sw.ElapsedMilliseconds:#,#;;0}ms"); onProgress.Invoke(result.Progress); } using (database.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext context)) { SmugglerRestore(_restoreConfiguration.BackupLocation, database, context, databaseRecord, onProgress, result); result.DatabaseRecord.Processed = true; result.Documents.Processed = true; result.RevisionDocuments.Processed = true; result.Conflicts.Processed = true; result.Indexes.Processed = true; result.Counters.Processed = true; onProgress.Invoke(result.Progress); databaseRecord.Topology = new DatabaseTopology(); // restoring to the current node only databaseRecord.Topology.Members.Add(_nodeTag); databaseRecord.Disabled = true; // we are currently restoring, shouldn't try to access it _serverStore.EnsureNotPassive(); DisableOngoingTasksIfNeeded(databaseRecord); var(index, _) = await _serverStore.WriteDatabaseRecordAsync(databaseName, databaseRecord, null, restoreSettings.DatabaseValues, isRestore : true); await _serverStore.Cluster.WaitForIndexNotification(index); // restore identities & cmpXchg values RestoreFromLastFile(onProgress, database, lastFile, context, result); } } // after the db for restore is done, we can safely set the db status to active databaseRecord = _serverStore.LoadDatabaseRecord(databaseName, out _); databaseRecord.Disabled = false; var(updateIndex, _) = await _serverStore.WriteDatabaseRecordAsync(databaseName, databaseRecord, null); await _serverStore.Cluster.WaitForIndexNotification(updateIndex); return(result); } catch (Exception e) { if (Logger.IsOperationsEnabled) { Logger.Operations("Failed to restore database", e); } var alert = AlertRaised.Create( _restoreConfiguration.DatabaseName, "Failed to restore database", $"Could not restore database named {_restoreConfiguration.DatabaseName}", AlertType.RestoreError, NotificationSeverity.Error, details: new ExceptionDetails(e)); _serverStore.NotificationCenter.Add(alert); if (_serverStore.LoadDatabaseRecord(_restoreConfiguration.DatabaseName, out var _) == null) { // delete any files that we already created during the restore IOExtensions.DeleteDirectory(_restoreConfiguration.DataDirectory); } else { var deleteResult = await _serverStore.DeleteDatabaseAsync(_restoreConfiguration.DatabaseName, true, new[] { _serverStore.NodeTag }); await _serverStore.Cluster.WaitForIndexNotification(deleteResult.Index); } result.AddError($"Error occurred during restore of database {databaseName}. Exception: {e.Message}"); onProgress.Invoke(result.Progress); throw; } finally { _operationCancelToken.Dispose(); } }