VolumeManagerDatabaseHelper.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. /* Copyright (C) 2014-2016 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. namespace DiskAccessLibrary.LogicalDiskManager
  11. {
  12. public class VolumeManagerDatabaseHelper
  13. {
  14. public static void ConvertRaidToStripedVolume(DiskGroupDatabase database, Guid volumeGuid)
  15. {
  16. List<DatabaseRecord> records = new List<DatabaseRecord>();
  17. VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volumeGuid);
  18. if (volumeRecord == null)
  19. {
  20. throw new MissingDatabaseRecordException("Volume record is missing");
  21. }
  22. volumeRecord = (VolumeRecord)volumeRecord.Clone();
  23. volumeRecord.VolumeTypeString = "gen";
  24. volumeRecord.ReadPolicy = ReadPolicyName.Select;
  25. volumeRecord.VolumeFlags = VolumeFlags.DefaultUnknown | VolumeFlags.Writeback;
  26. records.Add(volumeRecord);
  27. ComponentRecord componentRecord = database.FindComponentsByVolumeID(volumeRecord.VolumeId)[0];
  28. if (componentRecord == null)
  29. {
  30. throw new MissingDatabaseRecordException("Component record is missing");
  31. }
  32. componentRecord = (ComponentRecord)componentRecord.Clone();
  33. componentRecord.ExtentLayout = ExtentLayoutName.Stripe;
  34. records.Add(componentRecord);
  35. database.UpdateDatabase(records);
  36. }
  37. public static void ConvertStripedVolumeToRaid(DiskGroupDatabase database, Guid volumeGuid)
  38. {
  39. List<DatabaseRecord> records = new List<DatabaseRecord>();
  40. VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volumeGuid);
  41. if (volumeRecord == null)
  42. {
  43. throw new MissingDatabaseRecordException("Volume record is missing");
  44. }
  45. volumeRecord = (VolumeRecord)volumeRecord.Clone();
  46. volumeRecord.VolumeTypeString = "raid5";
  47. volumeRecord.ReadPolicy = ReadPolicyName.RAID;
  48. volumeRecord.VolumeFlags = VolumeFlags.DefaultUnknown | VolumeFlags.Writeback | VolumeFlags.Writecopy;
  49. records.Add(volumeRecord);
  50. ComponentRecord componentRecord = database.FindComponentsByVolumeID(volumeRecord.VolumeId)[0];
  51. if (componentRecord == null)
  52. {
  53. throw new MissingDatabaseRecordException("Component record is missing");
  54. }
  55. componentRecord = (ComponentRecord)componentRecord.Clone();
  56. componentRecord.ExtentLayout = ExtentLayoutName.RAID5;
  57. records.Add(componentRecord);
  58. database.UpdateDatabase(records);
  59. }
  60. /// <summary>
  61. /// Update the database (add the new extent)
  62. /// </summary>
  63. /// <param name="volume">RAID-5 or striped volume</param>
  64. /// <returns>new extent ID</returns>
  65. public static ulong AddNewExtentToVolume(DiskGroupDatabase database, DynamicVolume volume, DiskExtent newExtent)
  66. {
  67. PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(newExtent.Disk);
  68. List<DatabaseRecord> records = new List<DatabaseRecord>();
  69. VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
  70. if (volumeRecord == null)
  71. {
  72. throw new MissingDatabaseRecordException("Volume record is missing");
  73. }
  74. volumeRecord = (VolumeRecord)volumeRecord.Clone();
  75. volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(newExtent.TotalSectors, privateHeader);
  76. records.Add(volumeRecord);
  77. ComponentRecord componentRecord = database.FindComponentsByVolumeID(volumeRecord.VolumeId)[0];
  78. if (componentRecord == null)
  79. {
  80. throw new MissingDatabaseRecordException("Component record is missing");
  81. }
  82. componentRecord = (ComponentRecord)componentRecord.Clone();
  83. componentRecord.NumberOfExtents++;
  84. componentRecord.NumberOfColumns++;
  85. records.Add(componentRecord);
  86. DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid);
  87. if (diskRecord == null)
  88. {
  89. throw new MissingDatabaseRecordException("Disk record is missing");
  90. }
  91. diskRecord = (DiskRecord)diskRecord.Clone();
  92. records.Add(diskRecord);
  93. ExtentRecord newExtentRecord = new ExtentRecord();
  94. newExtentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name);
  95. newExtentRecord.ComponentId = componentRecord.ComponentId;
  96. newExtentRecord.DiskId = diskRecord.DiskId;
  97. newExtentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(newExtent.FirstSector, privateHeader);
  98. newExtentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(newExtent.TotalSectors, privateHeader);
  99. newExtentRecord.HasColumnIndexFlag = true;
  100. newExtentRecord.ColumnIndex = (uint)volume.Columns.Count; // zero based
  101. records.Add(newExtentRecord);
  102. // we should update the disk records and extent records
  103. foreach (DynamicDiskExtent extent in volume.Extents)
  104. {
  105. ExtentRecord extentRecord = database.FindExtentByExtentID(extent.ExtentID);
  106. if (extentRecord == null)
  107. {
  108. throw new MissingDatabaseRecordException("Extent record is missing");
  109. }
  110. extentRecord = (ExtentRecord)extentRecord.Clone();
  111. records.Add(extentRecord);
  112. diskRecord = database.FindDiskByDiskID(extentRecord.DiskId);
  113. if (diskRecord == null)
  114. {
  115. throw new MissingDatabaseRecordException("Disk record is missing");
  116. }
  117. // there could be multiple extents on the same disk, make sure we only add each disk once
  118. if (!records.Contains(diskRecord))
  119. {
  120. diskRecord = (DiskRecord)diskRecord.Clone();
  121. records.Add(diskRecord);
  122. }
  123. }
  124. database.UpdateDatabase(records);
  125. return newExtentRecord.ExtentId;
  126. }
  127. /// <summary>
  128. /// Update the database to point to the new extent location (same or different disk)
  129. /// </summary>
  130. public static void UpdateExtentLocation(DiskGroupDatabase database, DynamicVolume volume, DynamicDiskExtent relocatedExtent)
  131. {
  132. PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(relocatedExtent.Disk);
  133. DiskRecord targetDiskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid);
  134. VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
  135. List<DatabaseRecord> records = new List<DatabaseRecord>();
  136. ExtentRecord sourceExtentRecord = database.FindExtentByExtentID(relocatedExtent.ExtentID);
  137. ExtentRecord relocatedExtentRecord = (ExtentRecord)sourceExtentRecord.Clone();
  138. relocatedExtentRecord.DiskId = targetDiskRecord.DiskId;
  139. relocatedExtentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(relocatedExtent.FirstSector, privateHeader);
  140. records.Add(relocatedExtentRecord);
  141. // we should update the disk records
  142. foreach (DynamicDiskExtent extent in volume.Extents)
  143. {
  144. DiskRecord diskRecord = database.FindDiskByDiskID(relocatedExtentRecord.DiskId);
  145. // there could be multiple extents on the same disk, make sure we only add each disk once
  146. if (!records.Contains(diskRecord))
  147. {
  148. diskRecord = (DiskRecord)diskRecord.Clone();
  149. records.Add(diskRecord);
  150. }
  151. }
  152. // when moving to a new disk, we should update the new disk record as well
  153. if (!records.Contains(targetDiskRecord))
  154. {
  155. records.Add(targetDiskRecord.Clone());
  156. }
  157. database.UpdateDatabase(records);
  158. }
  159. public static void ExtendSimpleVolume(DiskGroupDatabase database, SimpleVolume volume, long numberOfAdditionalSectors)
  160. {
  161. VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
  162. volumeRecord = (VolumeRecord)volumeRecord.Clone();
  163. volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalSectors, volume.BytesPerSector);
  164. ExtentRecord extentRecord = database.FindExtentByExtentID(volume.DiskExtent.ExtentID);
  165. extentRecord = (ExtentRecord)extentRecord.Clone();
  166. extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalSectors, volume.BytesPerSector);
  167. DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs
  168. diskRecord = (DiskRecord)diskRecord.Clone();
  169. List<DatabaseRecord> records = new List<DatabaseRecord>();
  170. records.Add(volumeRecord);
  171. records.Add(extentRecord);
  172. records.Add(diskRecord);
  173. database.UpdateDatabase(records);
  174. }
  175. public static void ExtendStripedVolume(DiskGroupDatabase database, StripedVolume volume, long numberOfAdditionalExtentSectors)
  176. {
  177. if (numberOfAdditionalExtentSectors % volume.SectorsPerStripe > 0)
  178. {
  179. throw new ArgumentException("Number of additional sectors must be multiple of stripes per sector");
  180. }
  181. List<DatabaseRecord> records = new List<DatabaseRecord>();
  182. VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
  183. volumeRecord = (VolumeRecord)volumeRecord.Clone();
  184. volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors * volume.NumberOfColumns, volume.BytesPerSector);
  185. records.Add(volumeRecord);
  186. // we only want to extend the last extent in each column
  187. foreach (DynamicColumn column in volume.Columns)
  188. {
  189. DynamicDiskExtent lastExtent = column.Extents[column.Extents.Count - 1];
  190. ExtentRecord extentRecord = database.FindExtentByExtentID(lastExtent.ExtentID);
  191. extentRecord = (ExtentRecord)extentRecord.Clone();
  192. extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors, volume.BytesPerSector);
  193. records.Add(extentRecord);
  194. DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs
  195. diskRecord = (DiskRecord)diskRecord.Clone();
  196. records.Add(diskRecord);
  197. }
  198. database.UpdateDatabase(records);
  199. }
  200. public static void ExtendRAID5Volume(DiskGroupDatabase database, Raid5Volume volume, long numberOfAdditionalExtentSectors)
  201. {
  202. if (numberOfAdditionalExtentSectors % volume.SectorsPerStripe > 0)
  203. {
  204. throw new ArgumentException("Number of additional sectors must be multiple of stripes per sector");
  205. }
  206. List<DatabaseRecord> records = new List<DatabaseRecord>();
  207. VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
  208. volumeRecord = (VolumeRecord)volumeRecord.Clone();
  209. volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors * (volume.NumberOfColumns - 1), volume.BytesPerSector);
  210. records.Add(volumeRecord);
  211. foreach (DynamicColumn column in volume.Columns)
  212. {
  213. DynamicDiskExtent lastExtent = column.Extents[column.Extents.Count - 1];
  214. ExtentRecord extentRecord = database.FindExtentByExtentID(lastExtent.ExtentID);
  215. extentRecord = (ExtentRecord)extentRecord.Clone();
  216. extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors, volume.BytesPerSector);
  217. records.Add(extentRecord);
  218. DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs
  219. diskRecord = (DiskRecord)diskRecord.Clone();
  220. records.Add(diskRecord);
  221. }
  222. database.UpdateDatabase(records);
  223. }
  224. public static ulong CreateSimpleVolume(DiskGroupDatabase database, DiskExtent extent)
  225. {
  226. List<DatabaseRecord> records = new List<DatabaseRecord>();
  227. VolumeRecord volumeRecord = new VolumeRecord();
  228. volumeRecord.Id = database.AllocateNewRecordID();
  229. volumeRecord.Name = GetNextSimpleVolumeName(database.VolumeRecords);
  230. volumeRecord.VolumeTypeString = "gen";
  231. volumeRecord.StateString = "ACTIVE";
  232. volumeRecord.ReadPolicy = ReadPolicyName.Select;
  233. volumeRecord.VolumeNumber = GetNextVolumeNumber(database.VolumeRecords);
  234. volumeRecord.VolumeFlags = VolumeFlags.Writeback | VolumeFlags.DefaultUnknown;
  235. volumeRecord.NumberOfComponents = 1;
  236. volumeRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extent.TotalSectors, extent.BytesPerSector);
  237. volumeRecord.PartitionType = PartitionType.RAW;
  238. volumeRecord.VolumeGuid = Guid.NewGuid();
  239. records.Add(volumeRecord);
  240. ComponentRecord componentRecord = new ComponentRecord();
  241. componentRecord.Id = database.AllocateNewRecordID();
  242. componentRecord.Name = volumeRecord.Name + "-01";
  243. componentRecord.StateString = "ACTIVE";
  244. componentRecord.ExtentLayout = ExtentLayoutName.Concatenated;
  245. componentRecord.NumberOfExtents = 1;
  246. componentRecord.VolumeId = volumeRecord.VolumeId;
  247. componentRecord.HasStripedExtentsFlag = false;
  248. componentRecord.NumberOfColumns = 0;
  249. records.Add(componentRecord);
  250. // we should update the disk record
  251. PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(extent.Disk);
  252. DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid);
  253. diskRecord = (DiskRecord)diskRecord.Clone();
  254. records.Add(diskRecord);
  255. ExtentRecord extentRecord = new ExtentRecord();
  256. extentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name);
  257. extentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(extent.FirstSector, privateHeader);
  258. extentRecord.SizeLBA = volumeRecord.SizeLBA;
  259. extentRecord.ComponentId = componentRecord.ComponentId;
  260. extentRecord.DiskId = diskRecord.DiskId;
  261. extentRecord.HasColumnIndexFlag = false;
  262. records.Add(extentRecord);
  263. database.UpdateDatabase(records);
  264. return volumeRecord.VolumeId;
  265. }
  266. public static ulong CreateRAID5Volume(DiskGroupDatabase database, List<DiskExtent> extents, bool isDegraded)
  267. {
  268. int numberOfColumns;
  269. if (isDegraded)
  270. {
  271. numberOfColumns = extents.Count + 1;
  272. }
  273. else
  274. {
  275. numberOfColumns = extents.Count;
  276. }
  277. List<DatabaseRecord> records = new List<DatabaseRecord>();
  278. VolumeRecord volumeRecord = new VolumeRecord();
  279. volumeRecord.Id = database.AllocateNewRecordID();
  280. volumeRecord.Name = GetNextRAIDVolumeName(database.VolumeRecords);
  281. volumeRecord.VolumeTypeString = "raid5";
  282. volumeRecord.StateString = "ACTIVE";
  283. volumeRecord.ReadPolicy = ReadPolicyName.RAID;
  284. volumeRecord.VolumeNumber = GetNextVolumeNumber(database.VolumeRecords);
  285. volumeRecord.VolumeFlags = VolumeFlags.Writeback | VolumeFlags.Writecopy | VolumeFlags.DefaultUnknown;
  286. volumeRecord.NumberOfComponents = 1;
  287. volumeRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extents[0].TotalSectors * (numberOfColumns - 1), extents[0].BytesPerSector);
  288. volumeRecord.PartitionType = PartitionType.RAW;
  289. volumeRecord.VolumeGuid = Guid.NewGuid();
  290. records.Add(volumeRecord);
  291. ComponentRecord componentRecord = new ComponentRecord();
  292. componentRecord.Id = database.AllocateNewRecordID();
  293. componentRecord.Name = volumeRecord.Name + "-01";
  294. componentRecord.StateString = "ACTIVE";
  295. componentRecord.ExtentLayout = ExtentLayoutName.RAID5;
  296. componentRecord.NumberOfExtents = (uint)numberOfColumns;
  297. componentRecord.VolumeId = volumeRecord.VolumeId;
  298. componentRecord.HasStripedExtentsFlag = true;
  299. componentRecord.StripeSizeLBA = 128; // 64KB - the default
  300. componentRecord.NumberOfColumns = (uint)numberOfColumns;
  301. records.Add(componentRecord);
  302. for(int index = 0; index < extents.Count; index++)
  303. {
  304. DiskExtent extent = extents[index];
  305. // we should update the disk records
  306. PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(extent.Disk);
  307. DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid);
  308. diskRecord = (DiskRecord)diskRecord.Clone();
  309. records.Add(diskRecord);
  310. ExtentRecord extentRecord = new ExtentRecord();
  311. extentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name);
  312. extentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(extent.FirstSector, privateHeader);
  313. extentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extent.TotalSectors, extent.BytesPerSector);
  314. extentRecord.ComponentId = componentRecord.ComponentId;
  315. extentRecord.DiskId = diskRecord.DiskId;
  316. extentRecord.HasColumnIndexFlag = (index > 0);
  317. extentRecord.ColumnIndex = (uint)index; // zero based
  318. records.Add(extentRecord);
  319. }
  320. if (isDegraded)
  321. {
  322. // we have to make-up a disk
  323. // The DiskFlags and ExtentFlags are not necessary (they will be added later anyway when the disk group is reimported)
  324. DiskRecord diskRecord = new DiskRecord();
  325. diskRecord.Id = database.AllocateNewRecordID();
  326. diskRecord.Name = "Miss" + new Random().Next(100);
  327. diskRecord.DiskGuid = Guid.NewGuid();
  328. diskRecord.DiskFlags = DiskFlags.Detached;
  329. records.Add(diskRecord);
  330. ExtentRecord extentRecord = new ExtentRecord();
  331. extentRecord.Name = diskRecord.Name + "-01";
  332. extentRecord.ExtentFlags = ExtentFlags.Recover;
  333. extentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extents[0].TotalSectors, extents[0].BytesPerSector);
  334. extentRecord.ComponentId = componentRecord.ComponentId;
  335. extentRecord.DiskId = diskRecord.DiskId;
  336. extentRecord.HasColumnIndexFlag = true;
  337. extentRecord.ColumnIndex = (uint)extents.Count; // zero based
  338. records.Add(extentRecord);
  339. }
  340. database.UpdateDatabase(records);
  341. return volumeRecord.VolumeId;
  342. }
  343. private static string GetNextSimpleVolumeName(List<VolumeRecord> volumeRecords)
  344. {
  345. return GetNextVolumeName(volumeRecords, "Volume");
  346. }
  347. private static string GetNextSpannedVolumeName(List<VolumeRecord> volumeRecords)
  348. {
  349. return GetNextVolumeName(volumeRecords, "Volume");
  350. }
  351. private static string GetNextStripedVolumeName(List<VolumeRecord> volumeRecords)
  352. {
  353. return GetNextVolumeName(volumeRecords, "Stripe");
  354. }
  355. private static string GetNextRAIDVolumeName(List<VolumeRecord> volumeRecords)
  356. {
  357. return GetNextVolumeName(volumeRecords, "Raid");
  358. }
  359. private static string GetNextVolumeName(List<VolumeRecord> volumeRecords, string prefix)
  360. {
  361. int index = 1;
  362. while (true)
  363. {
  364. string name = prefix + index.ToString();
  365. bool isNameAvailable = true;
  366. foreach (VolumeRecord volumeRecord in volumeRecords)
  367. {
  368. if (volumeRecord.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
  369. {
  370. isNameAvailable = false;
  371. break;
  372. }
  373. }
  374. if (isNameAvailable)
  375. {
  376. return name;
  377. }
  378. index++;
  379. }
  380. }
  381. /// <param name="extentRecords">Could be all of the database records or just the relevant records</param>
  382. public static string GetNextExtentName(List<ExtentRecord> extentRecords, string diskName)
  383. {
  384. int index = 1;
  385. while (true)
  386. {
  387. string name = String.Format("{0}-{1}", diskName, index.ToString("00"));
  388. bool isNameAvailable = true;
  389. foreach (ExtentRecord extentRecord in extentRecords)
  390. {
  391. if (extentRecord.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
  392. {
  393. isNameAvailable = false;
  394. break;
  395. }
  396. }
  397. if (isNameAvailable)
  398. {
  399. return name;
  400. }
  401. index++;
  402. }
  403. }
  404. public static uint GetNextVolumeNumber(List<VolumeRecord> volumeRecords)
  405. {
  406. // volume number starts with 5 (probably because 1-4 are reserved for partitions)
  407. uint volumeNumber = 5;
  408. foreach (VolumeRecord record in volumeRecords)
  409. {
  410. if (record.VolumeNumber >= volumeNumber)
  411. {
  412. volumeNumber = record.VolumeNumber + 1;
  413. }
  414. }
  415. return volumeNumber;
  416. }
  417. }
  418. }