AddDiskToArrayHelper.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Text;
  10. using DiskAccessLibrary;
  11. using Utilities;
  12. namespace DiskAccessLibrary.LogicalDiskManager
  13. {
  14. public class AddDiskToArrayHelper
  15. {
  16. public static void AddDiskToRaid5Volume(List<DynamicDisk> disks, Raid5Volume volume, DiskExtent newExtent, ref long bytesCopied)
  17. {
  18. DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid);
  19. if (database == null)
  20. {
  21. throw new DatabaseNotFoundException();
  22. }
  23. // If there will be a power failure during the conversion, our RAID volume will resync during boot,
  24. // To prevent destruction of the data, we temporarily convert the array to striped volume
  25. VolumeManagerDatabaseHelper.ConvertRaidToStripedVolume(database, volume.VolumeGuid);
  26. ulong newExtentID = VolumeManagerDatabaseHelper.AddNewExtentToVolume(database, volume, newExtent);
  27. // Backup the first sector of the first extent to the last sector of the new extent
  28. // (We replace the filesystem boot record with our own sector for recovery purposes)
  29. byte[] filesystemBootRecord = volume.Extents[0].ReadSector(0);
  30. newExtent.WriteSectors(newExtent.TotalSectors - 1, filesystemBootRecord);
  31. AddDiskOperationBootRecord resumeRecord = new AddDiskOperationBootRecord();
  32. resumeRecord.VolumeGuid = volume.VolumeGuid;
  33. PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(newExtent.Disk);
  34. // privateHeader cannot be null at this point
  35. resumeRecord.NumberOfCommittedSectors = 0;
  36. // we use volume.WriteSectors so that the parity information will be update
  37. // this way, we could recover the first sector of each extent if a disk will fail
  38. volume.WriteSectors(0, resumeRecord.GetBytes());
  39. ResumeAddDiskToRaid5Volume(disks, volume, new DynamicDiskExtent(newExtent, newExtentID), resumeRecord, ref bytesCopied);
  40. }
  41. public static void ResumeAddDiskToRaid5Volume(List<DynamicDisk> disks, StripedVolume stripedVolume, AddDiskOperationBootRecord resumeRecord, ref long bytesCopied)
  42. {
  43. List<DynamicColumn> columns = stripedVolume.Columns;
  44. DynamicDiskExtent newExtent = columns[columns.Count - 1].Extents[0];
  45. columns.RemoveAt(columns.Count - 1);
  46. Raid5Volume volume = new Raid5Volume(columns, stripedVolume.SectorsPerStripe, stripedVolume.VolumeGuid, stripedVolume.DiskGroupGuid);
  47. volume.VolumeID = stripedVolume.VolumeID;
  48. volume.Name = stripedVolume.Name;
  49. volume.DiskGroupName = stripedVolume.DiskGroupName;
  50. ResumeAddDiskToRaid5Volume(disks, volume, newExtent, resumeRecord, ref bytesCopied);
  51. }
  52. private static void ResumeAddDiskToRaid5Volume(List<DynamicDisk> disks, Raid5Volume volume, DynamicDiskExtent newExtent, AddDiskOperationBootRecord resumeRecord, ref long bytesCopied)
  53. {
  54. // When reading from the volume, we must use the old volume (without the new disk)
  55. // However, when writing the boot sector to the volume, we must use the new volume or otherwise parity information will be invalid
  56. List<DynamicColumn> newVolumeColumns = new List<DynamicColumn>();
  57. newVolumeColumns.AddRange(volume.Columns);
  58. newVolumeColumns.Add(new DynamicColumn(newExtent));
  59. Raid5Volume newVolume = new Raid5Volume(newVolumeColumns, volume.SectorsPerStripe, volume.VolumeGuid, volume.DiskGroupGuid);
  60. int oldColumnCount = volume.Columns.Count;
  61. int newColumnCount = oldColumnCount + 1;
  62. long resumeFromStripe = (long)resumeRecord.NumberOfCommittedSectors / volume.SectorsPerStripe;
  63. // it would be prudent to write the new extent before committing to the operation, however, it would take much longer.
  64. // The number of sectors in extent / column is always a multiple of SectorsPerStripe.
  65. // We read enough stripes to write a vertical stripe segment in the new array,
  66. // We will read MaximumTransferSizeLBA and make sure maximumStripesToTransfer is multiple of (NumberOfColumns - 1).
  67. int maximumStripesToTransfer = (Settings.MaximumTransferSizeLBA / volume.SectorsPerStripe) / (newColumnCount - 1) * (newColumnCount - 1);
  68. long totalStripesInVolume = volume.TotalStripes;
  69. long stripeIndexInVolume = resumeFromStripe;
  70. while (stripeIndexInVolume < totalStripesInVolume)
  71. {
  72. // When we add a column, the distance between the stripes we read (later in the column) to thes one we write (earlier),
  73. // Is growing constantly (because we can stack more stripes in each vertical stripe), so we increment the number of stripes we
  74. // can safely transfer as we go.
  75. // (We assume that the segment we write will be corrupted if there will be a power failure)
  76. long stripeToReadIndexInColumn = stripeIndexInVolume / (oldColumnCount - 1);
  77. long stripeToWriteIndexInColumn = stripeIndexInVolume / (newColumnCount - 1);
  78. long numberOfStripesSafeToTransfer = (stripeToReadIndexInColumn - stripeToWriteIndexInColumn) * (newColumnCount - 1);
  79. bool verticalStripeAtRisk = (numberOfStripesSafeToTransfer == 0);
  80. if (numberOfStripesSafeToTransfer == 0)
  81. {
  82. // The first few stripes in each column are 'at rist', meaning that we may overwrite crucial data (that is only stored in memory)
  83. // when writing the segment that will be lost forever if a power failure will occur during the write operation.
  84. // Note: The number of 'at risk' vertical stripes is equal to the number of columns in the old array - 1
  85. numberOfStripesSafeToTransfer = (newColumnCount - 1);
  86. }
  87. int numberOfStripesToTransfer = (int)Math.Min(numberOfStripesSafeToTransfer, maximumStripesToTransfer);
  88. long stripesLeft = totalStripesInVolume - stripeIndexInVolume;
  89. numberOfStripesToTransfer = (int)Math.Min(numberOfStripesToTransfer, stripesLeft);
  90. byte[] segmentData = volume.ReadStripes(stripeIndexInVolume, numberOfStripesToTransfer);
  91. if (numberOfStripesToTransfer % (newColumnCount - 1) > 0)
  92. {
  93. // this is the last segment and we need to zero-fill it for the write:
  94. int numberOfStripesToWrite = (int)Math.Ceiling((double)numberOfStripesToTransfer / (newColumnCount - 1)) * (newColumnCount - 1);
  95. byte[] temp = new byte[numberOfStripesToWrite * volume.BytesPerStripe];
  96. Array.Copy(segmentData, temp, segmentData.Length);
  97. segmentData = temp;
  98. }
  99. long firstStripeIndexInColumn = stripeIndexInVolume / (newColumnCount - 1);
  100. if (verticalStripeAtRisk)
  101. {
  102. // we write 'at risk' stripes one at a time to the new volume, this will make sure they will not overwrite crucial data
  103. // (because they will be written in an orderly fashion, and not in bulk from the first column to the last)
  104. newVolume.WriteStripes(stripeIndexInVolume, segmentData);
  105. }
  106. else
  107. {
  108. WriteSegment(volume, newExtent, firstStripeIndexInColumn, segmentData);
  109. }
  110. // update resume record
  111. resumeRecord.NumberOfCommittedSectors += (ulong)(numberOfStripesToTransfer * volume.SectorsPerStripe);
  112. bytesCopied = (long)resumeRecord.NumberOfCommittedSectors * volume.BytesPerSector;
  113. newVolume.WriteSectors(0, resumeRecord.GetBytes());
  114. stripeIndexInVolume += numberOfStripesToTransfer;
  115. }
  116. // we're done, let's restore the filesystem boot record
  117. byte[] filesystemBootRecord = newExtent.ReadSector(newExtent.TotalSectors - 1);
  118. newVolume.WriteSectors(0, filesystemBootRecord);
  119. DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid);
  120. VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid);
  121. }
  122. /// <summary>
  123. /// Segment - sequence of stripes that is a multiple of (NumberOfColumns - 1),
  124. /// and every (NumberOfColumns - 1) stripes in the sequence have the same stripeIndexInColumn
  125. /// (Such sequence can be written to disk without reading the parity information first)
  126. /// </summary>
  127. public static void WriteSegment(Raid5Volume volume, DynamicDiskExtent newExtent, long firstStripeIndexInColumn, byte[] data)
  128. {
  129. List<DynamicColumn> newArray = new List<DynamicColumn>();
  130. newArray.AddRange(volume.Columns);
  131. newArray.Add(new DynamicColumn(newExtent));
  132. int bytesPerStripe = volume.BytesPerStripe;
  133. int stripesToWritePerColumn = (data.Length / bytesPerStripe) / (newArray.Count - 1);
  134. int dataLengthPerColumn = stripesToWritePerColumn * bytesPerStripe;
  135. byte[][] columnData = new byte[newArray.Count][];
  136. for(int index = 0; index < columnData.Length; index++)
  137. {
  138. columnData[index] = new byte[dataLengthPerColumn];
  139. }
  140. Parallel.For(0, stripesToWritePerColumn, delegate(int stripeOffsetInColumn)
  141. {
  142. long stripeIndexInColumn = firstStripeIndexInColumn + stripeOffsetInColumn;
  143. int parityColumnIndex = (newArray.Count - 1) - (int)(stripeIndexInColumn % newArray.Count);
  144. byte[] parityData = new byte[bytesPerStripe];
  145. for (int stripeVerticalIndex = 0; stripeVerticalIndex < newArray.Count - 1; stripeVerticalIndex++)
  146. {
  147. int columnIndex = (parityColumnIndex + 1 + stripeVerticalIndex) % newArray.Count;
  148. long stripeOffsetInData = (stripeOffsetInColumn * (newArray.Count - 1) + stripeVerticalIndex) * bytesPerStripe;
  149. Array.Copy(data, stripeOffsetInData, columnData[columnIndex], stripeOffsetInColumn * bytesPerStripe, bytesPerStripe);
  150. parityData = ByteUtils.XOR(parityData, 0, columnData[columnIndex], stripeOffsetInColumn * bytesPerStripe, bytesPerStripe);
  151. }
  152. Array.Copy(parityData, 0, columnData[parityColumnIndex], stripeOffsetInColumn * bytesPerStripe, bytesPerStripe);
  153. });
  154. // write the data
  155. long firstSectorIndexInColumn = firstStripeIndexInColumn * volume.SectorsPerStripe;
  156. for (int columnIndex = 0; columnIndex < newArray.Count; columnIndex++)
  157. {
  158. newArray[columnIndex].WriteSectors(firstSectorIndexInColumn, columnData[columnIndex]);
  159. }
  160. }
  161. }
  162. }