private static void MoveExtentLeft(List<DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied) { DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); if (database == null) { throw new DatabaseNotFoundException(); } DynamicDiskExtent relocatedExtent = DynamicDiskExtentHelper.GetByExtentID(volume.DynamicExtents, resumeRecord.ExtentID); if (resumeRecord.OldStartSector == (ulong)relocatedExtent.FirstSector) { // the database update was not completed (this must be a resume operation) relocatedExtent = new DynamicDiskExtent(relocatedExtent.Disk, (long)resumeRecord.NewStartSector, relocatedExtent.Size, resumeRecord.ExtentID); VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, relocatedExtent); } DiskExtent sourceExtent = new DiskExtent(relocatedExtent.Disk, (long)resumeRecord.OldStartSector, relocatedExtent.Size); MoveHelper.MoveExtentDataLeft(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied); // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume if (resumeRecord.RestoreRAID5) { VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); // get the updated volume (we just reconverted to RAID-5) volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid); } // restore the filesystem boot sector byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector); volume.WriteSectors(0, filesystemBootRecord); ClearBackupData(relocatedExtent.Disk, resumeRecord); }
private static void MoveExtentRight(List <DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied) { DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); if (database == null) { throw new DatabaseNotFoundException(); } int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, resumeRecord.ExtentID); DynamicDiskExtent sourceExtent = volume.DynamicExtents[extentIndex]; DiskExtent relocatedExtent = new DiskExtent(sourceExtent.Disk, (long)resumeRecord.NewStartSector, sourceExtent.Size); MoveHelper.MoveExtentDataRight(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied); // even if the database update won't complete, the resume record was copied // update the database DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID); dynamicRelocatedExtent.Name = sourceExtent.Name; dynamicRelocatedExtent.DiskGuid = sourceExtent.DiskGuid; VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent); // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume if (resumeRecord.RestoreRAID5) { VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); } // get the updated volume (we moved an extent and possibly reconverted to RAID-5) volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid); // restore the filesystem boot sector byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector); volume.WriteSectors(0, filesystemBootRecord); ClearBackupData(relocatedExtent.Disk, resumeRecord); }
private static void ResumeAddDiskToRaid5Volume(List <DynamicDisk> disks, Raid5Volume volume, DynamicDiskExtent newExtent, AddDiskOperationBootRecord resumeRecord, ref long bytesCopied) { // When reading from the volume, we must use the old volume (without the new disk) // However, when writing the boot sector to the volume, we must use the new volume or otherwise parity information will be invalid List <DynamicColumn> newVolumeColumns = new List <DynamicColumn>(); newVolumeColumns.AddRange(volume.Columns); newVolumeColumns.Add(new DynamicColumn(newExtent)); Raid5Volume newVolume = new Raid5Volume(newVolumeColumns, volume.SectorsPerStripe, volume.VolumeGuid, volume.DiskGroupGuid); int oldColumnCount = volume.Columns.Count; int newColumnCount = oldColumnCount + 1; long resumeFromStripe = (long)resumeRecord.NumberOfCommittedSectors / volume.SectorsPerStripe; // it would be prudent to write the new extent before committing to the operation, however, it would take much longer. // The number of sectors in extent / column is always a multiple of SectorsPerStripe. // We read enough stripes to write a vertical stripe segment in the new array, // We will read MaximumTransferSizeLBA and make sure maximumStripesToTransfer is multiple of (NumberOfColumns - 1). int maximumStripesToTransfer = (Settings.MaximumTransferSizeLBA / volume.SectorsPerStripe) / (newColumnCount - 1) * (newColumnCount - 1); long totalStripesInVolume = volume.TotalStripes; long stripeIndexInVolume = resumeFromStripe; while (stripeIndexInVolume < totalStripesInVolume) { // When we add a column, the distance between the stripes we read (later in the column) to thes one we write (earlier), // Is growing constantly (because we can stack more stripes in each vertical stripe), so we increment the number of stripes we // can safely transfer as we go. // (We assume that the segment we write will be corrupted if there will be a power failure) long stripeToReadIndexInColumn = stripeIndexInVolume / (oldColumnCount - 1); long stripeToWriteIndexInColumn = stripeIndexInVolume / (newColumnCount - 1); long numberOfStripesSafeToTransfer = (stripeToReadIndexInColumn - stripeToWriteIndexInColumn) * (newColumnCount - 1); bool verticalStripeAtRisk = (numberOfStripesSafeToTransfer == 0); if (numberOfStripesSafeToTransfer == 0) { // The first few stripes in each column are 'at rist', meaning that we may overwrite crucial data (that is only stored in memory) // when writing the segment that will be lost forever if a power failure will occur during the write operation. // Note: The number of 'at risk' vertical stripes is equal to the number of columns in the old array - 1 numberOfStripesSafeToTransfer = (newColumnCount - 1); } int numberOfStripesToTransfer = (int)Math.Min(numberOfStripesSafeToTransfer, maximumStripesToTransfer); long stripesLeft = totalStripesInVolume - stripeIndexInVolume; numberOfStripesToTransfer = (int)Math.Min(numberOfStripesToTransfer, stripesLeft); byte[] segmentData = volume.ReadStripes(stripeIndexInVolume, numberOfStripesToTransfer); if (numberOfStripesToTransfer % (newColumnCount - 1) > 0) { // this is the last segment and we need to zero-fill it for the write: int numberOfStripesToWrite = (int)Math.Ceiling((decimal)numberOfStripesToTransfer / (newColumnCount - 1)) * (newColumnCount - 1); byte[] temp = new byte[numberOfStripesToWrite * volume.BytesPerStripe]; Array.Copy(segmentData, temp, segmentData.Length); segmentData = temp; } long firstStripeIndexInColumn = stripeIndexInVolume / (newColumnCount - 1); if (verticalStripeAtRisk) { // we write 'at risk' stripes one at a time to the new volume, this will make sure they will not overwrite crucial data // (because they will be written in an orderly fashion, and not in bulk from the first column to the last) newVolume.WriteStripes(stripeIndexInVolume, segmentData); } else { WriteSegment(volume, newExtent, firstStripeIndexInColumn, segmentData); } // update resume record resumeRecord.NumberOfCommittedSectors += (ulong)(numberOfStripesToTransfer * volume.SectorsPerStripe); bytesCopied = (long)resumeRecord.NumberOfCommittedSectors * volume.BytesPerSector; newVolume.WriteSectors(0, resumeRecord.GetBytes()); stripeIndexInVolume += numberOfStripesToTransfer; } // we're done, let's restore the filesystem boot record byte[] filesystemBootRecord = newExtent.ReadSector(newExtent.TotalSectors - 1); newVolume.WriteSectors(0, filesystemBootRecord); DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); }