/// <summary> /// /// </summary> /// <param name="backupItem"></param> /// <param name="restore"></param> /// <remarks> /// <paramref name="restore"/> must have backup device already added /// </remarks> private void AddMoveFileInstructionsIfNeeded(BackupItem backupItem, Restore restore) { Check.DoRequireArgumentNotNull(restore, "restore"); Check.DoCheckArgument(restore.Devices.Count > 0, "Restore instance must have backup device already added"); if (backupItem.BackupType == BackupType.Full) { var databaseFilesInBackup = ReadDatabaseFileList(restore); foreach (var dbFile in databaseFilesInBackup) { var targetDir = dbFile.IsLog ? DefaultLogPath : DefaultDataPath; var physicalFilePath = FindExistingDatabaseFile(dbFile.LogicalName, dbFile.IsLog); if (string.IsNullOrEmpty(physicalFilePath)) { // no such file in the existing database; maybe it was dropped physicalFilePath = Path.Combine(targetDir, Path.GetFileName(dbFile.PhysicalName)); physicalFilePath = PickNewFileName(physicalFilePath); } Log.InfoFormat("Moving {0} to {1}", dbFile.LogicalName, physicalFilePath); restore.RelocateFiles.Add(new RelocateFile(dbFile.LogicalName, physicalFilePath)); } } }
/// <summary> /// Constructor. /// </summary> /// <param name="backupFilesystemNameParser"> /// Parser to delegate <see cref="IBackupFilesystemNameParser"/>'s method calls to. /// </param> /// <param name="endOfPeriodCalculator"> /// Optional; if null, <see cref="GetPeriodEnd"/> will always return null /// </param> public TimePeriodFromFilesystemNameExtractor(IBackupFilesystemNameParser backupFilesystemNameParser, Func <DateTime, DateTime> endOfPeriodCalculator) { Check.DoRequireArgumentNotNull(backupFilesystemNameParser, "BackupFilesystemNameParser"); BackupFilesystemNameParser = backupFilesystemNameParser; EndOfPeriodCalculator = endOfPeriodCalculator; }
/// <param name="rootPath"> /// Root path under which per-database backup folders are found; the folder names must match database names exactly. /// </param> /// <param name="targetTime"> /// Optional, point in time to restore to; latest if null /// </param> /// <param name="worker"> /// Optional, to report progress to and implement cancellation from. /// </param> /// <param name="targetServer"> /// Self-explanatory /// </param> /// <param name="namingConvention"> /// Defines historical backup directory structure. /// </param> public MultiRestorer(string rootPath, DateTime?targetTime, BackgroundWorker worker, string targetServer, IBackupDirectoryNamingConvention namingConvention) { Check.DoRequireArgumentNotNull(rootPath, "rootPath"); Check.DoRequireArgumentNotNull(targetServer, "targetServer"); Check.DoRequireArgumentNotNull(namingConvention, "namingConvention"); NamingConvention = namingConvention; RootPath = rootPath; TargetTime = targetTime; _worker = worker; var bld = new SqlConnectionStringBuilder() { IntegratedSecurity = true, DataSource = targetServer, InitialCatalog = "master", ConnectTimeout = 2, // seconds; }; // keep same connection to be able to use single user mode for databases when restoring var conn = new ServerConnection(new SqlConnection(bld.ToString())); Server = new Server(conn); Server.ConnectionContext.StatementTimeout = 0; // read actual names var existingLogins = string.Join(", ", Server.Logins.Cast <Login>().Select(x => x.Name)); Log.InfoFormat("Existing logins: {0}", existingLogins); }
/// <returns> /// null if file contains no valid backups /// </returns> private BackupFileHeader ParseBackupHeader(FileInfo fileInfo) { Check.DoRequireArgumentNotNull(fileInfo, "fileInfo"); BackupFileHeader result = null; var allBackupItems = SqlServerProxy.GetBackupItems(fileInfo); var validItems = GetValidBackups(allBackupItems); var wrongItems = GetInvalidBackups(allBackupItems); if (wrongItems.Count > 0) { Log.WarnFormat("File {0} contains {1} backups from database other than {2}; ignoring them", fileInfo.FullName, wrongItems.Count, DatabaseName); } if (validItems.Count == 0) { Log.WarnFormat("File {0} contains no backups for database {1}; skipping it.", fileInfo.FullName, DatabaseName); } else { result = new BackupFileHeader(fileInfo, validItems); } return(result); }
public BackupFileFolderInfo GetContainingFolder(BackupFileInfo backupFile) { Check.DoRequireArgumentNotNull(backupFile, "backupFile"); return(_backupFileFolders .Where(f => f.DirectoryInfo.Name.Equals(backupFile.FileInfo.Directory.Name, StringComparison.InvariantCultureIgnoreCase)) .SingleOrDefault()); }
/// <summary> /// Initializes a new instance of the <see cref="T:System.Object"/> class. /// </summary> public DatabaseRestorer(IBackupFileCatalog backupCatalog, string databaseName, MultiRestorer multiRestorer) { Check.DoRequireArgumentNotNull(backupCatalog, "backupCatalog"); _backupCatalog = backupCatalog; _databaseName = databaseName; _multiRestorer = multiRestorer; _preExistingDatabase = Database != null; }
public RestorePlanner(IBackupFileCatalog backupFileCatalog, ISqlServerProxy sqlServerProxy, string databaseName) { Check.DoRequireArgumentNotNull(backupFileCatalog, "backupFileCatalog"); Check.DoRequireArgumentNotNull(sqlServerProxy, "sqlServerProxy"); Check.DoRequireArgumentNotNull(databaseName, "databaseName"); BackupFileCatalog = backupFileCatalog; SqlServerProxy = sqlServerProxy; DatabaseName = databaseName; }
/// <summary> /// Factory method creating catalog for calendar month folders and default 'month-day-day' file naming convention. /// </summary> public static BackupFileCatalog CreateDefaultMonthDayDayCatalog(string databaseBackupFolder) { Check.DoRequireArgumentNotNull(databaseBackupFolder, "databaseBackupFolder"); var namingConvention = BackupDirectoryNamingConvention.CreateDefaultMonthDayDayConvention(); var result = new BackupFileCatalog(namingConvention, databaseBackupFolder); return(result); }
public BackupFileHeader(FileInfo fileInfo, List <BackupItem> validItems) { Check.DoRequireArgumentNotNull(fileInfo, "fileInfo"); Check.DoRequireArgumentNotNull(validItems, "validItems"); Check.DoCheckArgument(validItems.Count > 0, "No valid backup items"); FileInfo = fileInfo; FirstBackupTime = validItems.First().BackupStartTime; LastBackupTime = validItems[validItems.Count - 1].BackupStartTime; ValidItems = new List <BackupItem>(validItems); }
/// <summary> /// Factory method creating catalog for calendar month folders and given file naming convention. /// </summary> /// <param name="fileNamingConvention"> /// File naming convention to use. /// </param> public static BackupFileCatalog CreateMonthlyCatalog(string databaseBackupFolder, IBackupFileNamingConvention fileNamingConvention) { Check.DoRequireArgumentNotNull(databaseBackupFolder, "databaseBackupFolder"); Check.DoRequireArgumentNotNull(fileNamingConvention, "fileNamingConvention"); var namingConvention = BackupDirectoryNamingConvention.CreateMonthlyConvention(fileNamingConvention); var result = new BackupFileCatalog(namingConvention, databaseBackupFolder); return(result); }
public BackupFileCatalog( IBackupDirectoryNamingConvention namingConvention , string databaseBackupFolder) { Check.DoRequireArgumentNotNull(namingConvention, "namingConvention"); Check.DoRequireArgumentNotNull(databaseBackupFolder, "databaseBackupFolder"); NamingConvention = namingConvention; DatabaseBackupDirectory = new DirectoryInfo(databaseBackupFolder); Check.DoCheckArgument(DatabaseBackupDirectory.Exists, () => string.Format("Backup folder {0} does not exist", databaseBackupFolder)); _backupFileFolders = GetBackupFolders(); }
/// <summary> /// /// </summary> /// <param name="timestampExtractorExpression"> /// Must capture timestamp in the first capture of the first group. /// </param> /// <param name="timestampFormat"> /// </param> /// <param name="databaseNameExtractorExpression"> /// Optional /// </param> public BackupFilesystemObjectNameParser(string timestampExtractorExpression, string timestampFormat, string databaseNameExtractorExpression) { Check.DoRequireArgumentNotNull(timestampExtractorExpression, "timestampExtractorExpression"); Check.DoRequireArgumentNotNull(timestampFormat, "timestampFormat"); _timestampExtractor = new Regex(timestampExtractorExpression, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); _timestampFormat = timestampFormat; if (!string.IsNullOrEmpty(databaseNameExtractorExpression)) { _databaseNameExtractor = new Regex(databaseNameExtractorExpression, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } }
/// <summary> /// Factory method creating convention for calendar month folders and given file naming convention. /// </summary> /// <param name="fileNamingConvention"> /// File naming convention to use. /// </param> /// <returns> /// </returns> public static BackupDirectoryNamingConvention CreateMonthlyConvention(IBackupFileNamingConvention fileNamingConvention) { Check.DoRequireArgumentNotNull(fileNamingConvention, "fileNamingConvention"); var monthDirectoryNameParser = new BackupFilesystemObjectNameParser( "(.+)", RestoreBackupLib.BackupFileNamingConvention.DefaultMonthTimestampFormat, null); var monthPeriodExtractor = TimePeriodFromFilesystemNameExtractor.CreateForCalendarMonths(monthDirectoryNameParser); var result = new BackupDirectoryNamingConvention(monthPeriodExtractor, fileNamingConvention); return(result); }
public BackupFileFolderInfo(IBackupFileNamingConvention fileNamingConvention, DirectoryInfo directoryInfo, DateTime startTime, DateTime?endTime) { Check.DoRequireArgumentNotNull(fileNamingConvention, "fileNamingConvention"); Check.DoRequireArgumentNotNull(directoryInfo, "directoryInfo"); Check.DoCheckArgument(endTime > startTime, "Time period"); DirectoryInfo = directoryInfo; StartTime = startTime; PeriodEndDeclaredExplicitly = endTime.HasValue; EndTime = endTime.HasValue ? endTime.Value : directoryInfo.LastWriteTime; BackupFileNamingConvention = fileNamingConvention; }
private string Extract(string input, Regex regex) { Check.DoRequireArgumentNotNull(input, "input"); Check.DoRequireArgumentNotNull(regex, "regex"); var match = regex.Match(input); if (match.Success) { // first occurrence such as when capturing subexpression repeats: @"\b(\w+\s*)+\."; return(match.Groups[1].Captures[0].Value); } return(null); }
/// <summary> /// Constructor of immutable instance. /// </summary> /// <param name="backupType"> /// Type of backups contained in this file. /// </param> /// <param name="startTime"> /// Explicitly defined start of the period in which backups were written into the file. If null, the start time will be taken from creation time. /// </param> /// <param name="endTime"> /// Explicitly defined end of the period in which backups were written into the file. /// </param> /// <param name="fileInfo"> /// </param> /// <remarks> /// Note that <see cref="StartTime"/> will always have a value while <see cref="EndTime"/> may be null. This is because start time is more important and /// must be figured out in any case while EndTime is not essential and only beneficial for optimization. /// If simultaneous log and database backups are allowed, without end time we only need to start from first log prior to last database backup. /// However, if there is no simultaneous backup, things are much easier and we can work without end time. /// What happens if diff backup is marked with the hour when it is made and single daily log backup is marked with start of day timestamp? /// Then we need end time or have to start from first log backup file prior to the database backup. /// </remarks> public BackupFileInfo(SupportedBackupType backupType, DateTime?startTime, DateTime?endTime, FileInfo fileInfo) { Check.DoRequireArgumentNotNull(fileInfo, "fileInfo"); Check.DoCheckArgument(backupType != SupportedBackupType.None, "Invalid file type"); BackupType = backupType; FileInfo = fileInfo; PeriodStartDeclaredExplicitly = startTime.HasValue; StartTime = startTime ?? fileInfo.CreationTime; PeriodEndDeclaredExplicitly = endTime.HasValue; EndTime = endTime ?? fileInfo.LastWriteTime; }
public List <BackupItem> GetBackupItems(FileInfo file) { Check.DoRequireArgumentNotNull(file, "file"); var restore = new Restore(); restore.Devices.AddDevice(file.FullName, DeviceType.File); var headerTable = restore.ReadBackupHeader(Server); var result = new List <BackupItem>(headerTable.Rows.Count); foreach (DataRow row in headerTable.Rows) { result.Add(GetBackupItem(row, file)); } return(result); }
private List <BackupFileInfo> GetBackupFiles(List <FileInfo> files) { Check.DoRequireArgumentNotNull(files, "files"); var result = new List <BackupFileInfo>(files.Count); var filteredFiles = files.Where(f => BackupFileNamingConvention.GetBackupType(f.Name) != SupportedBackupType.None); foreach (var info in filteredFiles) { var backupFileInfo = BackupFileNamingConvention.GetBackupFileInfo(info); result.Add(backupFileInfo); } result = result.OrderBy(f => f.StartTime).ToList(); InferEndTimeWhereNotDefinedExplicitly(result); return(result); }
/// <summary> /// Verified sequence of backup items to restore to achieve configured outcome. /// Never returns null. /// </summary> /// <param name="lastDatabaseBackupItem"></param> /// <returns></returns> private List <BackupItem> GetLogBackupItems(BackupItem lastDatabaseBackupItem, DateTime?targetTime) { Check.DoRequireArgumentNotNull(lastDatabaseBackupItem, "lastDatabaseBackupItem"); var result = new List <BackupItem>(1000); var logFiles = BackupFileCatalog.GetLogBackupsSequence(lastDatabaseBackupItem.BackupStartTime); // need only first log backup after target time; flag indicates that file is already processed var targetReached = false; var fileIterator = logFiles.GetEnumerator(); var lastBackupItem = lastDatabaseBackupItem; while (!targetReached && fileIterator.MoveNext()) { var header = ParseBackupHeader(fileIterator.Current.FileInfo); if (header != null) { var itemIterator = header.ValidItems.GetEnumerator(); while (!targetReached && itemIterator.MoveNext()) { var item = itemIterator.Current; Check.DoAssertLambda(CheckSequence(lastBackupItem, item, false), () => $"Item #{item.Position} in file {item.FileInfo.Name} cannot be applied after item #{lastBackupItem.Position} in" + $" file {lastBackupItem.FileInfo.Name}"); lastBackupItem = item; result.Add(item); targetReached = (targetTime.HasValue && item.BackupStartTime > targetTime.Value); } } } return(result); }
/// <summary> /// Add new backup type mapping. /// </summary> /// <param name="nameSuffix"> /// Suffix of backup files, including extension, case insensitive /// </param> /// <param name="backupType"> /// Backup type /// </param> /// <param name="timePeriodExtractor"> /// Object implementing extraction of relevant information from file name, such as time period. /// </param> public void AddType(string nameSuffix, SupportedBackupType backupType, ITimePeriodFromFilesystemNameExtractor timePeriodExtractor) { Check.DoRequireArgumentNotNull(nameSuffix, "nameSuffix"); Check.DoRequireArgumentNotNull(timePeriodExtractor, "timePeriodExtractor"); Check.DoCheckArgument(null == GetTypeInfo(backupType), string.Format("Parser for {0} already mapped", backupType)); Check.DoCheckArgument(backupType != SupportedBackupType.None, "Invalid backup type (None)"); var intersectedType = _backupTypes.Values.Where( i => i.FileNameSuffix.EndsWith(nameSuffix, StringComparison.InvariantCultureIgnoreCase) || nameSuffix.EndsWith(i.FileNameSuffix, StringComparison.InvariantCultureIgnoreCase)) .FirstOrDefault(); Check.DoCheckArgument( intersectedType == null , () => string.Format("Suffix {0} for {1} conflicts with suffix {2} for type {3}" , nameSuffix, backupType, intersectedType.FileNameSuffix, intersectedType.BackupType)); var newTypeInfo = new BackupTypeInfo() { FileNameSuffix = nameSuffix, BackupType = backupType, TimePeriodExtractor = timePeriodExtractor }; _backupTypes.Add(backupType, newTypeInfo); }
private void Restore(BackupItem item) { Check.DoRequireArgumentNotNull(item, "item"); Log.InfoFormat("{0}: restoring {1} from {2}", DatabaseName, item, item.FileInfo.FullName); var actionType = item.BackupType == BackupType.Log ? RestoreActionType.Log : RestoreActionType.Database; var restore = CreateRestoreInstance(); restore.Action = actionType; restore.Devices.AddDevice(item.FileInfo.FullName, DeviceType.File); restore.FileNumber = item.Position; if (TargetTime.HasValue && item.BackupType == BackupType.Log) { // note that another format, yyyy-MM-ddTHH:mm:ss.fff failed with error saying "already restored past the point" restore.ToPointInTime = TargetTime.Value.ToString("MMM dd, yyyy hh:mm:ss tt"); Log.InfoFormat("Setting Point In Time {0} on {1}", restore.ToPointInTime, item); } AddMoveFileInstructionsIfNeeded(item, restore); restore.PercentCompleteNotification = 10; restore.PercentComplete += RestoreOnPercentComplete; try { Server.ConnectionContext.ExecuteNonQuery("use master;"); restore.SqlRestore(Server); } finally { restore.PercentComplete -= RestoreOnPercentComplete; } }
public SqlServer2014Proxy(Server server) { Check.DoRequireArgumentNotNull(server, "server"); Server = server; }
/// <summary> /// /// </summary> /// <param name="fileName"></param> /// <returns> /// null if not mapped /// </returns> private BackupTypeInfo GetTypeInfo(string fileName) { Check.DoRequireArgumentNotNull(fileName, "fileSystemName"); return(_backupTypes.Values.Where(t => fileName.EndsWith(t.FileNameSuffix, StringComparison.InvariantCultureIgnoreCase)) .FirstOrDefault()); }