示例#1
0
        private async Task <bool> CheckIfSnapshotIsEncrypted(string fullPath)
        {
            using (var zip = await GetZipArchive(fullPath))
            {
                foreach (var zipEntry in zip.Entries)
                {
                    if (string.Equals(zipEntry.FullName, RestoreSettings.SettingsFileName, StringComparison.OrdinalIgnoreCase))
                    {
                        using (var entryStream = zipEntry.Open())
                        {
                            var json = _context.Read(entryStream, "read database settings");
                            json.BlittableValidation();

                            RestoreSettings restoreSettings = JsonDeserializationServer.RestoreSettings(json);
                            return(restoreSettings.DatabaseRecord.Encrypted);
                        }
                    }
                }
            }

            throw new InvalidOperationException("Can't find settings file in backup archive.");
        }
示例#2
0
        private static bool CheckIfSnapshotIsEncrypted(string filePath, TransactionOperationContext context)
        {
            using (var zip = ZipFile.Open(filePath, ZipArchiveMode.Read, System.Text.Encoding.UTF8))
            {
                foreach (var zipEntry in zip.Entries)
                {
                    if (string.Equals(zipEntry.FullName, RestoreSettings.SettingsFileName, StringComparison.OrdinalIgnoreCase))
                    {
                        using (var entryStream = zipEntry.Open())
                        {
                            var json = context.Read(entryStream, "read database settings");
                            json.BlittableValidation();

                            RestoreSettings restoreSettings = JsonDeserializationServer.RestoreSettings(json);
                            return(restoreSettings.DatabaseRecord.Encrypted);
                        }
                    }
                }
            }

            throw new InvalidOperationException("Can't find settings file in backup archive.");
        }
示例#3
0
        protected async Task <RestoreSettings> SnapshotRestore(JsonOperationContext context, string backupPath,
                                                               Action <IOperationProgress> onProgress, RestoreResult restoreResult)
        {
            Debug.Assert(onProgress != null);

            RestoreSettings restoreSettings = null;

            var fullBackupPath = GetBackupPath(backupPath);

            using (var zip = await GetZipArchiveForSnapshot(fullBackupPath))
            {
                foreach (var zipEntries in zip.Entries.GroupBy(x => x.FullName.Substring(0, x.FullName.Length - x.Name.Length)))
                {
                    var directory = zipEntries.Key;

                    if (string.IsNullOrWhiteSpace(directory))
                    {
                        foreach (var zipEntry in zipEntries)
                        {
                            if (string.Equals(zipEntry.Name, RestoreSettings.SettingsFileName, StringComparison.OrdinalIgnoreCase))
                            {
                                using (var entryStream = zipEntry.Open())
                                {
                                    var snapshotEncryptionKey = RestoreFromConfiguration.EncryptionKey != null
                                        ? Convert.FromBase64String(RestoreFromConfiguration.EncryptionKey)
                                        : null;

                                    using (var stream = GetInputStream(entryStream, snapshotEncryptionKey))
                                    {
                                        var json = context.Read(stream, "read database settings for restore");
                                        json.BlittableValidation();

                                        restoreSettings = JsonDeserializationServer.RestoreSettings(json);

                                        restoreSettings.DatabaseRecord.DatabaseName = RestoreFromConfiguration.DatabaseName;
                                        DatabaseHelper.Validate(RestoreFromConfiguration.DatabaseName, restoreSettings.DatabaseRecord, _serverStore.Configuration);

                                        if (restoreSettings.DatabaseRecord.Encrypted && _hasEncryptionKey == false)
                                        {
                                            throw new ArgumentException("Database snapshot is encrypted but the encryption key is missing!");
                                        }

                                        if (restoreSettings.DatabaseRecord.Encrypted == false && _hasEncryptionKey)
                                        {
                                            throw new ArgumentException("Cannot encrypt a non encrypted snapshot backup during restore!");
                                        }
                                    }
                                }
                            }
                        }

                        continue;
                    }

                    var voronDataDirectory = new VoronPathSetting(RestoreFromConfiguration.DataDirectory);
                    var restoreDirectory   = directory.StartsWith(Constants.Documents.PeriodicBackup.Folders.Documents, StringComparison.OrdinalIgnoreCase)
                        ? voronDataDirectory
                        : voronDataDirectory.Combine(directory);

                    BackupMethods.Full.Restore(
                        zipEntries,
                        restoreDirectory,
                        journalDir: null,
                        onProgress: message =>
                    {
                        restoreResult.AddInfo(message);
                        restoreResult.SnapshotRestore.ReadCount++;
                        onProgress.Invoke(restoreResult.Progress);
                    },
                        cancellationToken: _operationCancelToken.Token);
                }
            }

            if (restoreSettings == null)
            {
                throw new InvalidDataException("Cannot restore the snapshot without the settings file!");
            }

            return(restoreSettings);
        }
示例#4
0
        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();
            }
        }
示例#5
0
        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();
            }
        }
示例#6
0
        private RestoreSettings SnapshotRestore(
            string backupPath,
            string dataDirectory,
            Action <IOperationProgress> onProgress,
            RestoreResult restoreResult)
        {
            Debug.Assert(onProgress != null);

            RestoreSettings restoreSettings = null;

            var voronBackupPath    = new VoronPathSetting(backupPath);
            var voronDataDirectory = new VoronPathSetting(dataDirectory);

            using (var zip = ZipFile.Open(voronBackupPath.FullPath, ZipArchiveMode.Read, System.Text.Encoding.UTF8))
            {
                foreach (var zipEntries in zip.Entries.GroupBy(x => x.FullName.Substring(0, x.FullName.Length - x.Name.Length)))
                {
                    var directory = zipEntries.Key;

                    if (string.IsNullOrWhiteSpace(directory))
                    {
                        foreach (var zipEntry in zipEntries)
                        {
                            if (string.Equals(zipEntry.Name, RestoreSettings.SettingsFileName, StringComparison.OrdinalIgnoreCase))
                            {
                                using (var entryStream = zipEntry.Open())
                                    using (_serverStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                                    {
                                        var json = context.Read(entryStream, "read database settings for restore");
                                        json.BlittableValidation();

                                        restoreSettings = JsonDeserializationServer.RestoreSettings(json);

                                        restoreSettings.DatabaseRecord.DatabaseName = _restoreConfiguration.DatabaseName;
                                        DatabaseHelper.Validate(_restoreConfiguration.DatabaseName, restoreSettings.DatabaseRecord);

                                        if (restoreSettings.DatabaseRecord.Encrypted && _hasEncryptionKey == false)
                                        {
                                            throw new ArgumentException("Database snapshot is encrypted but the encryption key is missing!");
                                        }

                                        if (restoreSettings.DatabaseRecord.Encrypted == false && _hasEncryptionKey)
                                        {
                                            throw new ArgumentException("Cannot encrypt a non encrypted snapshot backup during restore!");
                                        }
                                    }
                            }
                        }

                        continue;
                    }

                    var restoreDirectory = directory.StartsWith(Constants.Documents.PeriodicBackup.Folders.Documents, StringComparison.OrdinalIgnoreCase)
                        ? voronDataDirectory
                        : voronDataDirectory.Combine(directory);

                    BackupMethods.Full.Restore(
                        zipEntries,
                        restoreDirectory,
                        journalDir: null,
                        onProgress: message =>
                    {
                        restoreResult.AddInfo(message);
                        restoreResult.SnapshotRestore.ReadCount++;
                        onProgress.Invoke(restoreResult.Progress);
                    },
                        cancellationToken: _operationCancelToken.Token);
                }
            }

            if (restoreSettings == null)
            {
                throw new InvalidDataException("Cannot restore the snapshot without the settings file!");
            }

            return(restoreSettings);
        }
        protected async Task <RestoreSettings> SnapshotRestore(JsonOperationContext context, string backupPath,
                                                               Action <IOperationProgress> onProgress, RestoreResult restoreResult)
        {
            Debug.Assert(onProgress != null);

            RestoreSettings restoreSettings = null;

            var fullBackupPath = GetBackupPath(backupPath);

            using (var zip = await GetZipArchiveForSnapshot(fullBackupPath))
            {
                var restorePath = new VoronPathSetting(RestoreFromConfiguration.DataDirectory);
                if (Directory.Exists(restorePath.FullPath) == false)
                {
                    Directory.CreateDirectory(restorePath.FullPath);
                }

                // validate free space
                var snapshotSize = zip.Entries.Sum(entry => entry.Length);
                BackupHelper.AssertFreeSpaceForSnapshot(restorePath.FullPath, snapshotSize, "restore a backup", Logger);

                foreach (var zipEntries in zip.Entries.GroupBy(x => x.FullName.Substring(0, x.FullName.Length - x.Name.Length)))
                {
                    var directory = zipEntries.Key;

                    if (string.IsNullOrWhiteSpace(directory))
                    {
                        foreach (var zipEntry in zipEntries)
                        {
                            if (string.Equals(zipEntry.Name, RestoreSettings.SettingsFileName, StringComparison.OrdinalIgnoreCase))
                            {
                                await using (var entryStream = zipEntry.Open())
                                {
                                    var snapshotEncryptionKey = RestoreFromConfiguration.EncryptionKey != null
                                        ? Convert.FromBase64String(RestoreFromConfiguration.EncryptionKey)
                                        : null;

                                    await using (var stream = GetInputStream(entryStream, snapshotEncryptionKey))
                                    {
                                        var json = await context.ReadForMemoryAsync(stream, "read database settings for restore");

                                        json.BlittableValidation();

                                        restoreSettings = JsonDeserializationServer.RestoreSettings(json);

                                        restoreSettings.DatabaseRecord.DatabaseName = RestoreFromConfiguration.DatabaseName;
                                        DatabaseHelper.Validate(RestoreFromConfiguration.DatabaseName, restoreSettings.DatabaseRecord, _serverStore.Configuration);

                                        if (restoreSettings.DatabaseRecord.Encrypted && _hasEncryptionKey == false)
                                        {
                                            throw new ArgumentException("Database snapshot is encrypted but the encryption key is missing!");
                                        }

                                        if (restoreSettings.DatabaseRecord.Encrypted == false && _hasEncryptionKey)
                                        {
                                            throw new ArgumentException("Cannot encrypt a non encrypted snapshot backup during restore!");
                                        }
                                    }
                                }
                            }
                        }

                        continue;
                    }

                    var restoreDirectory = directory.StartsWith(Constants.Documents.PeriodicBackup.Folders.Documents, StringComparison.OrdinalIgnoreCase)
                        ? restorePath
                        : restorePath.Combine(directory);

                    var isSubDirectory = PathUtil.IsSubDirectory(restoreDirectory.FullPath, restorePath.FullPath);
                    if (isSubDirectory == false)
                    {
                        var extensions = zipEntries
                                         .Select(x => Path.GetExtension(x.Name))
                                         .Distinct()
                                         .ToArray();

                        if (extensions.Length != 1 || string.Equals(extensions[0], TableValueCompressor.CompressionRecoveryExtension, StringComparison.OrdinalIgnoreCase) == false)
                        {
                            throw new InvalidOperationException($"Encountered invalid directory '{directory}' in snapshot file with following file extensions: {string.Join(", ", extensions)}");
                        }

                        // this enables backward compatibility of snapshot backups with compression recovery files before fix was made in RavenDB-17173
                        // the underlying issue was that we were putting full path when compression recovery files were backed up using snapshot
                        // because of that the end restore directory was not a sub-directory of a restore path
                        // which could result in a file already exists exception
                        // since restoring of compression recovery files is not mandatory then it is safe to skip them
                        continue;
                    }

                    BackupMethods.Full.Restore(
                        zipEntries,
                        restoreDirectory,
                        journalDir: null,
                        onProgress: message =>
                    {
                        restoreResult.AddInfo(message);
                        restoreResult.SnapshotRestore.ReadCount++;
                        onProgress.Invoke(restoreResult.Progress);
                    },
                        cancellationToken: _operationCancelToken.Token);
                }
            }

            if (restoreSettings == null)
            {
                throw new InvalidDataException("Cannot restore the snapshot without the settings file!");
            }

            return(restoreSettings);
        }