From 66c04024f945a8ce2f276e2d96bbe83c3a5add60 Mon Sep 17 00:00:00 2001 From: tigerblue77 <37409593+tigerblue77@users.noreply.github.com> Date: Wed, 20 Feb 2019 23:48:07 +0100 Subject: [PATCH 1/5] Adding of SHR RAID support and others functions to AbstractRaid class. --- Main.php | 39 ++++++++ src/AbstractRaid.php | 186 +++++++++++++++++++++++++++++++++----- src/Drive.php | 5 +- src/Raid/RaidFive.php | 37 +++++++- src/Raid/RaidOne.php | 34 ++++++- src/Raid/RaidSHR.php | 100 ++++++++++++++++++++ src/Raid/RaidSix.php | 35 ++++++- src/Raid/RaidTen.php | 36 +++++++- src/Raid/RaidZero.php | 34 ++++++- src/RaidFactory.php | 20 ++-- tests/DriveTest.php | 2 +- tests/RaidFactoryTest.php | 11 +++ 12 files changed, 490 insertions(+), 49 deletions(-) create mode 100644 Main.php create mode 100644 src/Raid/RaidSHR.php diff --git a/Main.php b/Main.php new file mode 100644 index 0000000..dfa8f3e --- /dev/null +++ b/Main.php @@ -0,0 +1,39 @@ + 0.9) { + $size /= $step; + $i++; + } + return number_format(round($size, $precision), $precision, ',', '.') . " " . $units[$i]; +} + +$RAIDTypes = array(0, 1, 5, 6, 10, 'SHR'); +$drives = [ + new Drive('1TB', 'hdd', 1), + new Drive('1TB', 'hdd', 2), + new Drive('250GB', 'hdd', 3), + new Drive('250GB', 'hdd', 4), + new Drive('160GB', 'hdd', 5), + new Drive('160GB', 'hdd', 6), +]; +$factory = new RaidFactory(); +$numberOfDecimalsToPrint = 2; + +foreach ($RAIDTypes as $RAIDType) { + $raid[$RAIDType] = $factory->create($RAIDType, $drives); + echo "RAID level : " . $raid[$RAIDType]->getLevel() . "\n"; + echo "RAID " . $raid[$RAIDType]->getLevel() . " total capacity : " . toHumanReadableSize($raid[$RAIDType]->getTotalCapacity(), $numberOfDecimalsToPrint) . "\n"; + echo "RAID " . $raid[$RAIDType]->getLevel() . " usable space : " . toHumanReadableSize($raid[$RAIDType]->getCapacity(), $numberOfDecimalsToPrint) . "\n"; + echo "RAID " . $raid[$RAIDType]->getLevel() . " lossed space : " . toHumanReadableSize($raid[$RAIDType]->getLossedSpace(), $numberOfDecimalsToPrint) . "\n"; + echo "RAID " . $raid[$RAIDType]->getLevel() . " parity size : " . toHumanReadableSize($raid[$RAIDType]->getParitySize(), $numberOfDecimalsToPrint) . "\n\n"; +} + +//var_dump($raidSHR); diff --git a/src/AbstractRaid.php b/src/AbstractRaid.php index 93a3db7..fdc13d6 100644 --- a/src/AbstractRaid.php +++ b/src/AbstractRaid.php @@ -2,6 +2,7 @@ namespace kevinquinnyo\Raid; use Cake\I18n\Number; +use Cake\Utility\Text; use kevinquinnyo\Raid\Drive; use RuntimeException; @@ -96,12 +97,14 @@ public function getLevel() /** * Get Drives * - * @param $options Options - add 'withHotSpares' to include hot spare drives. + * @param array $options Options - add 'withHotSpares' to include hot spare drives. * @return array The RAID's Drive objects. */ - public function getDrives($options = []) + public function getDrives(array $options = []) { - $options += ['withHotSpares' => false]; + $options += [ + 'withHotSpares' => false + ]; $drives = $this->drives; if ($options['withHotSpares'] === false) { @@ -141,7 +144,7 @@ public function getHotSpares() * @param array $drives An array of \kevinquinnyo\Raid\Drive Drive objects to set on the RAID. * @return self */ - public function setDrives($drives) + public function setDrives(array $drives) { $this->validate($drives); $this->drives = $drives; @@ -166,7 +169,7 @@ public function addDrive(Drive $drive) /** * Add Hot Spare * - * @param \kevinquinnyo\Raid\Drive $drive A Drive to add to the list of hot spares. + * @param \kevinquinnyo\Raid\Drive $drive A Drive to add to the list of hot spares drives. * @return self */ public function addHotSpare(Drive $drive) @@ -184,46 +187,102 @@ public function addHotSpare(Drive $drive) * Get the usable capacity of the RAID in its current state. * This method differs slightly per RAID level implementation. * - * @param array $options Additional options that the Raid objects methods allow. - * @return int The usable capacity of the RAID. + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to pass. + * @return int|string Usable capacity of the RAID in bytes or human readable format. */ abstract public function getCapacity(array $options = []); /** * Get Minimum Drive Size * - * Return the size in capacity of the smallest drive in the array. + * Return the capacity of the smallest drive in the array. + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * - withHotSpares - Whether to include the hot spares in the search. + * ``` * - * @return int Capacity of smallest drive in array. + * @param array $options Additional options to pass. + * @return int|string Capacity of the smallest drive in the array. */ - public function getMinimumDriveSize($options = []) + public function getMinimumDriveSize(array $options = []) { - $options += ['withHotSpares' => false]; - $floor = null; + $options += [ + 'human' => false, + 'withHotSpares' => false, + ]; + $floor = $this->drives[0]->getCapacity(); $drives = $this->getDrives($options); foreach ($drives as $drive) { - if (isset($floor) === false) { - $floor = $drive->getCapacity(); - } - if ($drive->getCapacity() < $floor) { $floor = $drive->getCapacity(); } } + if ($options['human'] === true) { + $floor = Number::toReadableSize($floor); + } + return $floor; } + /** + * Get Maximum Drive Size + * + * Return the capacity of the biggest drive in the array. + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * - withHotSpares - Whether to include the hot spares in the search. + * ``` + * + * @param array $options Additional options to pass. + * @return int|string Capacity of the biggest drive in the array. + */ + public function getMaximumDriveSize(array $options = []) + { + $options += [ + 'human' => false, + 'withHotSpares' => false, + ]; + $ceil = $this->drives[0]->getCapacity(); + $drives = $this->getDrives($options); + + foreach ($drives as $drive) { + if ($drive->getCapacity() > $ceil) { + $ceil = $drive->getCapacity(); + } + } + + if ($options['human'] === true) { + $ceil = Number::toReadableSize($ceil); + } + + return $ceil; + } + /** * Get Drive Count * * @param array $options Add 'withHotSpares' if you wish to include hot spares in the count. * @return int The drive count for the RAID. */ - public function getDriveCount($options = []) + public function getDriveCount(array $options = []) { - $options += ['withHotSpares' => false]; + $options += [ + 'withHotSpares' => false, + ]; $drives = $this->getDrives($options); return count($drives); @@ -249,14 +308,14 @@ public function getDriveCount($options = []) * ``` * * @param array $options Additional options to scope the results. - * @return int The total capacity for the RAID. + * @return int|string The total capacity for the RAID. */ - public function getTotalCapacity($options = []) + public function getTotalCapacity(array $options = []) { $options += [ 'human' => false, 'withHotSpares' => false, - 'floor' => true, + 'floor' => false, ]; $total = 0; $min = $this->getMinimumDriveSize(); @@ -302,4 +361,89 @@ public function validate(array $drives) return true; } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + abstract public function getParitySize(array $options = []); + + /** + * Get lossed space + * + * Get lossed space size (unusable and unused space, neither by parity nor by data). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * - withHotSpares - Whether to include the hot spares in the sum. + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The lossed space size of the RAID. + */ + public function getLossedSpace(array $options = []) + { + $options += [ + 'human' => false, + 'withHotSpares' => false, + ]; + $lossedSpace = $this->getTotalCapacity() - $this->getCapacity() - $this->getParitySize(); + if ($options['withHotSpares'] === true) { + foreach ($this->getHotSpares() as $hotSpareDrive) { + $lossedSpace += $hotSpareDrive->getCapacity(); + } + } + + if ($options['human'] === true) { + $lossedSpace = Number::toReadableSize($lossedSpace); + } + + return $lossedSpace; + } + + /** + * Get number of drives of the given capacity + * + * @param mixed $capacity The capacity of the drives you're looking for, in bytes or human readable format, e.g. - '500GB', '5T', etc. + * @param array $options Add 'withHotSpares' if you wish to include hot spares in the count. + * @return int The lossed capacity of the RAID. + */ + public function getNumberOfDrivesOfThisCapacity($capacity, array $options = []) + { + $options += [ + 'withHotSpares' => false + ]; + if (ctype_digit($capacity)) { + $bytes = (int)$capacity; + } + $capacity = isset($bytes) === true ? $bytes : (int)Text::parseFileSize($capacity); + + $numberOfDrivesOfThisCapacity = 0; + foreach ($this->drives as $drive) { + if ($drive->getCapacity() === $capacity) { + ++$numberOfDrivesOfThisCapacity; + } + } + if ($options['withHotSpares'] === true) { + foreach ($this->getHotSpares() as $hotSpareDrive) { + if ($hotSpareDrive->getCapacity() === $capacity) { + ++$numberOfDrivesOfThisCapacity; + } + } + } + + return $numberOfDrivesOfThisCapacity; + } } diff --git a/src/Drive.php b/src/Drive.php index 51142f5..42a033a 100644 --- a/src/Drive.php +++ b/src/Drive.php @@ -34,9 +34,10 @@ public function __construct($capacity, string $type, string $identifier, $option 'hotSpare' => false, ]; if (ctype_digit($capacity)) { - $bytes = (int)$capacity; + $this->capacity = (int)$capacity; + } else { + $this->capacity = (int)Text::parseFileSize($capacity); } - $this->capacity = isset($bytes) === true ? $bytes : Text::parseFileSize($capacity); $this->validate($type); $this->type = $type; $this->identifier = $identifier; diff --git a/src/Raid/RaidFive.php b/src/Raid/RaidFive.php index fc9fba4..4aea249 100644 --- a/src/Raid/RaidFive.php +++ b/src/Raid/RaidFive.php @@ -45,14 +45,41 @@ public function getCapacity(array $options = []) $options += [ 'human' => false, ]; - $total = $this->getTotalCapacity(); - $min = $this->getMinimumDriveSize(); - $result = $total === 0 ? $total : ($total - $min); + $totalCapacity = $this->getTotalCapacity(); + $capacity = $totalCapacity === 0 ? $totalCapacity : ($totalCapacity - $this->getMinimumDriveSize()); if ($options['human'] === true) { - return Number::toReadableSize($result); + return Number::toReadableSize($capacity); } - return $result; + return $capacity; + } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + public function getParitySize(array $options = []) + { + $options += [ + 'human' => false, + ]; + $paritySize = $this->getMinimumDriveSize(); + + if ($options['human'] === true) { + return Number::toReadableSize($paritySize); + } + + return $paritySize; } } diff --git a/src/Raid/RaidOne.php b/src/Raid/RaidOne.php index f8b2dc4..0b59051 100644 --- a/src/Raid/RaidOne.php +++ b/src/Raid/RaidOne.php @@ -44,10 +44,40 @@ public function getCapacity(array $options = []) $options += [ 'human' => false, ]; + $capacity = $this->getMinimumDriveSize(); + + if ($options['human'] === true) { + return Number::toReadableSize($capacity); + } + + return $capacity; + } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + public function getParitySize(array $options = []) + { + $options += [ + 'human' => false, + ]; + $capacity = $this->getMinimumDriveSize() * ($this->getDriveCount() - 1); + if ($options['human'] === true) { - return Number::toReadableSize($this->getMinimumDriveSize()); + return Number::toReadableSize($capacity); } - return $this->getMinimumDriveSize(); + return $capacity; } } diff --git a/src/Raid/RaidSHR.php b/src/Raid/RaidSHR.php new file mode 100644 index 0000000..c32dee1 --- /dev/null +++ b/src/Raid/RaidSHR.php @@ -0,0 +1,100 @@ +validate($drives); + } + + $this->setDrives($drives); + } + + /** + * Get Capacity + * + * Get the usable capacity of the RAID in its current state. + * This method differs slightly per RAID level implementation. + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to pass. + * @return int|string Usable capacity of the RAID in bytes or human readable format. + */ + public function getCapacity(array $options = []) + { + $options += [ + 'human' => false, + ]; + $capacity = $this->getTotalCapacity() - $this->getMaximumDriveSize(); + + if ($options['human'] === true) { + return Number::toReadableSize($capacity); + } + + return $capacity; + } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + public function getParitySize(array $options = []) + { + $options += [ + 'human' => false, + ]; + $minimumDriveSizeOfRAID = $this->getMinimumDriveSize(); + $maximumDriveSizeOfRAID = $this->getMaximumDriveSize(); + if ($this->getNumberOfDrivesOfThisCapacity($maximumDriveSizeOfRAID) >= 2) { + $paritySize = $maximumDriveSizeOfRAID; + } else { + /* We are looking for the second disk with the highest capacity present in the drives array, the use of the first will be the same as the capacity of the second */ + $paritySize = 0; + foreach ($this->drives as $drive) { + if ($drive->getCapacity() > $paritySize && $drive->getCapacity() < $maximumDriveSizeOfRAID) { + $paritySize = $drive->getCapacity(); + } + } + } + + if ($options['human'] === true) { + return Number::toReadableSize($paritySize); + } + + return $paritySize; + } +} diff --git a/src/Raid/RaidSix.php b/src/Raid/RaidSix.php index 6ffb8f1..8edcd77 100644 --- a/src/Raid/RaidSix.php +++ b/src/Raid/RaidSix.php @@ -46,11 +46,40 @@ public function getCapacity(array $options = []) ]; $total = $this->getTotalCapacity(); $count = $this->getDriveCount(); - $result = $total === 0 ? $total : $total / 2; + $capacity = $total === 0 ? $total : $total / 2; + + if ($options['human'] === true) { + return Number::toReadableSize($capacity); + } + + return $capacity; + } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + public function getParitySize(array $options = []) + { + $options += [ + 'human' => false, + ]; + $paritySize = $this->getMinimumDriveSize() * 2; + if ($options['human'] === true) { - return Number::toReadableSize($result); + return Number::toReadableSize($paritySize); } - return $result; + return $paritySize; } } diff --git a/src/Raid/RaidTen.php b/src/Raid/RaidTen.php index 5436fcf..abb4686 100644 --- a/src/Raid/RaidTen.php +++ b/src/Raid/RaidTen.php @@ -23,7 +23,6 @@ public function __construct(array $drives = []) if (empty($drives) === false) { $this->validate($drives); } - $this->setDrives($drives); } @@ -43,11 +42,40 @@ public function getCapacity(array $options = []) $options += [ 'human' => false, ]; - $result = $this->getTotalCapacity() / 2; + $capacity = $this->getTotalCapacity() / 2; + + if ($options['human'] === true) { + return Number::toReadableSize($capacity); + } + + return $capacity; + } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + public function getParitySize(array $options = []) + { + $options += [ + 'human' => false, + ]; + $paritySize = $this->getTotalCapacity() / 2; + if ($options['human'] === true) { - return Number::toReadableSize($result); + return Number::toReadableSize($paritySize); } - return $result; + return $paritySize; } } diff --git a/src/Raid/RaidZero.php b/src/Raid/RaidZero.php index 8bc76b8..d5a366a 100644 --- a/src/Raid/RaidZero.php +++ b/src/Raid/RaidZero.php @@ -44,12 +44,40 @@ public function getCapacity(array $options = []) $options += [ 'human' => false, ]; - $result = $this->getTotalCapacity(); + $capacity = $this->getTotalCapacity(); if ($options['human'] === true) { - return Number::toReadableSize($result); + return Number::toReadableSize($capacity); } - return $result; + return $capacity; + } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + public function getParitySize(array $options = []) + { + $options += [ + 'human' => false, + ]; + $paritySize = 0; + + if ($options['human'] === true) { + return Number::toReadableSize($paritySize); + } + + return $paritySize; } } diff --git a/src/RaidFactory.php b/src/RaidFactory.php index 41603d9..a8417cf 100644 --- a/src/RaidFactory.php +++ b/src/RaidFactory.php @@ -4,6 +4,7 @@ use InvalidArgumentException; use kevinquinnyo\Raid\Raid\RaidFive; use kevinquinnyo\Raid\Raid\RaidOne; +use kevinquinnyo\Raid\Raid\RaidSHR; use kevinquinnyo\Raid\Raid\RaidSix; use kevinquinnyo\Raid\Raid\RaidTen; use kevinquinnyo\Raid\Raid\RaidZero; @@ -18,31 +19,34 @@ class RaidFactory * @throws \InvalidArgumentException If the level provided is not supported by this library. * @return \kevinquinnyo\Raid\AbstractRaid Initialized Raid object. */ - public function create(int $level, $drives) + public function create($level, $drives) { $drives = (array)$drives; $raid = null; - switch ($level) { - case 0: + switch (true) { + case $level === 0: $raid = new RaidZero($drives); break; - case 1: + case $level === 1: $raid = new RaidOne($drives); break; - case 5: + case $level === 5: $raid = new RaidFive($drives); break; - case 6: + case $level === 6: $raid = new RaidSix($drives); break; - case 10: + case $level === 10: $raid = new RaidTen($drives); break; + case $level === 'SHR': + $raid = new RaidSHR($drives); + break; } if ($raid === null) { - throw new InvalidArgumentException('Unsupported RAID level provided. (Supported levels: 0, 1, 5, 6, 10)'); + throw new InvalidArgumentException('Unsupported RAID level provided. (Supported levels: 0, 1, 5, 6, 10, SHR)'); } return $raid; diff --git a/tests/DriveTest.php b/tests/DriveTest.php index e91391d..af57dc0 100644 --- a/tests/DriveTest.php +++ b/tests/DriveTest.php @@ -28,7 +28,7 @@ public function testSetIdentifier() public function testGetCapacity() { $drive = new Drive('1k', 'ssd', 1); - $this->assertSame(1024.0, $drive->getCapacity()); + $this->assertSame(1024, $drive->getCapacity()); } public function testGetCapacityWithHuman() diff --git a/tests/RaidFactoryTest.php b/tests/RaidFactoryTest.php index 098756a..b2ddce0 100644 --- a/tests/RaidFactoryTest.php +++ b/tests/RaidFactoryTest.php @@ -9,6 +9,7 @@ use \kevinquinnyo\Raid\Raid\RaidFive; use \kevinquinnyo\Raid\Raid\RaidSix; use \kevinquinnyo\Raid\Raid\RaidTen; +use \kevinquinnyo\Raid\Raid\RaidSHR; use InvalidArgumentException; class RaidFactoryTest extends TestCase @@ -73,4 +74,14 @@ public function testCreateRaidTen() $raid = $factory->create(10, $drives); $this->assertInstanceOf(RaidTen::class, $raid); } + public function testCreateSHRRaid() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + ]; + $factory = new RaidFactory(); + $raid = $factory->create('SHR', $drives); + $this->assertInstanceOf(RaidSHR::class, $raid); + } } From 220504f3d7458d71f7a36f0ec5327b5dfa825a64 Mon Sep 17 00:00:00 2001 From: tigerblue77 <37409593+tigerblue77@users.noreply.github.com> Date: Sat, 27 Apr 2019 20:23:08 +0200 Subject: [PATCH 2/5] Debugging RAID 5 --- src/Raid/RaidFive.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Raid/RaidFive.php b/src/Raid/RaidFive.php index 4aea249..ece6aa3 100644 --- a/src/Raid/RaidFive.php +++ b/src/Raid/RaidFive.php @@ -45,8 +45,7 @@ public function getCapacity(array $options = []) $options += [ 'human' => false, ]; - $totalCapacity = $this->getTotalCapacity(); - $capacity = $totalCapacity === 0 ? $totalCapacity : ($totalCapacity - $this->getMinimumDriveSize()); + $capacity = $this->getMinimumDriveSize() * ($this->getDriveCount() - 1); if ($options['human'] === true) { return Number::toReadableSize($capacity); From 6e4d5bd61e78a7bcd54343f067ae5e5746aa9731 Mon Sep 17 00:00:00 2001 From: tigerblue77 <37409593+tigerblue77@users.noreply.github.com> Date: Sat, 27 Apr 2019 20:23:49 +0200 Subject: [PATCH 3/5] Debugging RAID 6 --- src/Raid/RaidSix.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Raid/RaidSix.php b/src/Raid/RaidSix.php index 8edcd77..80ec0de 100644 --- a/src/Raid/RaidSix.php +++ b/src/Raid/RaidSix.php @@ -13,6 +13,7 @@ class RaidSix extends AbstractRaid protected $minimumDrives = 4; protected $mirrored = false; protected $parity = true; + protected $striped = true; /** * Constructor. @@ -44,9 +45,7 @@ public function getCapacity(array $options = []) $options += [ 'human' => false, ]; - $total = $this->getTotalCapacity(); - $count = $this->getDriveCount(); - $capacity = $total === 0 ? $total : $total / 2; + $capacity = $this->getMinimumDriveSize() * ($this->getDriveCount() - 2); if ($options['human'] === true) { return Number::toReadableSize($capacity); From a0f01748445ee0a0c551a9f47feb3f674fcf8e0f Mon Sep 17 00:00:00 2001 From: tigerblue77 <37409593+tigerblue77@users.noreply.github.com> Date: Sat, 27 Apr 2019 20:37:58 +0200 Subject: [PATCH 4/5] Adding RaidSHRTest.php file --- tests/AbstractRaidTest.php | 1 - tests/Raid/RaidSHRTest.php | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/Raid/RaidSHRTest.php diff --git a/tests/AbstractRaidTest.php b/tests/AbstractRaidTest.php index d2d7f82..ff4febf 100644 --- a/tests/AbstractRaidTest.php +++ b/tests/AbstractRaidTest.php @@ -4,7 +4,6 @@ use \PHPUnit\Framework\TestCase; use \kevinquinnyo\Raid\Drive; use \kevinquinnyo\Raid\AbstractRaid; -use \kevinquinnyo\Raid\Raid\RaidFive; use ReflectionClass; use RuntimeException; diff --git a/tests/Raid/RaidSHRTest.php b/tests/Raid/RaidSHRTest.php new file mode 100644 index 0000000..cbfc7e0 --- /dev/null +++ b/tests/Raid/RaidSHRTest.php @@ -0,0 +1,50 @@ +assertSame(2048, $raidSHR->getCapacity()); + $this->assertSame('2 KB', $raidSHR->getCapacity(['human' => true])); + } + public function testGetCapacityWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4, ['hotSpare' => true]), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + ]; + $raidSHR = new RaidSHR($drives); + $this->assertSame(2048, $raidSHR->getCapacity()); + } + public function testGetLevel() + { + $raidSHR = new RaidSHR(); + $this->assertSame('SHR', $raidSHR->getLevel()); + } + public function testGetParitySize() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4, ['hotSpare' => true]), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + ]; + $raidSHR = new RaidSHR($drives); + $this->assertSame(1024, $raidSHR->getParitySize()); + } +} From af039e3b1d1bb8cdb3dc89727954231cf2835878 Mon Sep 17 00:00:00 2001 From: tigerblue77 <37409593+tigerblue77@users.noreply.github.com> Date: Sun, 28 Apr 2019 16:01:50 +0200 Subject: [PATCH 5/5] Adding of SHR-2, bundle of tests and other code improvments --- Main.php | 19 +++- src/AbstractRaid.php | 177 ++++++++++++++++++++++++++---------- src/Drive.php | 15 ++- src/Raid/RaidFive.php | 4 +- src/Raid/RaidOne.php | 6 +- src/Raid/RaidSHR.php | 7 +- src/Raid/RaidSHR2.php | 131 ++++++++++++++++++++++++++ src/Raid/RaidSix.php | 4 +- src/Raid/RaidTen.php | 26 +++++- src/Raid/RaidZero.php | 5 +- src/RaidFactory.php | 4 + tests/AbstractRaidTest.php | 82 ++++++++++++++++- tests/Raid/RaidFiveTest.php | 29 ++++++ tests/Raid/RaidOneTest.php | 30 ++++++ tests/Raid/RaidSHR2Test.php | 71 +++++++++++++++ tests/Raid/RaidSHRTest.php | 20 +++- tests/Raid/RaidSixTest.php | 28 ++++++ tests/Raid/RaidTenTest.php | 46 +++++++++- tests/Raid/RaidZeroTest.php | 28 ++++++ tests/RaidFactoryTest.php | 15 ++- 20 files changed, 670 insertions(+), 77 deletions(-) create mode 100644 src/Raid/RaidSHR2.php create mode 100644 tests/Raid/RaidSHR2Test.php diff --git a/Main.php b/Main.php index dfa8f3e..5d28a96 100644 --- a/Main.php +++ b/Main.php @@ -15,7 +15,7 @@ function toHumanReadableSize($size, $precision = 1){ return number_format(round($size, $precision), $precision, ',', '.') . " " . $units[$i]; } -$RAIDTypes = array(0, 1, 5, 6, 10, 'SHR'); +$RAIDTypes = array(0, 1, 5, 6, 10, 'SHR', 'SHR2'); $drives = [ new Drive('1TB', 'hdd', 1), new Drive('1TB', 'hdd', 2), @@ -29,11 +29,20 @@ function toHumanReadableSize($size, $precision = 1){ foreach ($RAIDTypes as $RAIDType) { $raid[$RAIDType] = $factory->create($RAIDType, $drives); - echo "RAID level : " . $raid[$RAIDType]->getLevel() . "\n"; + echo "RAID level : " . $raid[$RAIDType]->getLevel() . "\n\n"; echo "RAID " . $raid[$RAIDType]->getLevel() . " total capacity : " . toHumanReadableSize($raid[$RAIDType]->getTotalCapacity(), $numberOfDecimalsToPrint) . "\n"; echo "RAID " . $raid[$RAIDType]->getLevel() . " usable space : " . toHumanReadableSize($raid[$RAIDType]->getCapacity(), $numberOfDecimalsToPrint) . "\n"; + echo "RAID " . $raid[$RAIDType]->getLevel() . " parity size : " . toHumanReadableSize($raid[$RAIDType]->getParitySize(), $numberOfDecimalsToPrint) . "\n"; echo "RAID " . $raid[$RAIDType]->getLevel() . " lossed space : " . toHumanReadableSize($raid[$RAIDType]->getLossedSpace(), $numberOfDecimalsToPrint) . "\n"; - echo "RAID " . $raid[$RAIDType]->getLevel() . " parity size : " . toHumanReadableSize($raid[$RAIDType]->getParitySize(), $numberOfDecimalsToPrint) . "\n\n"; + echo "RAID " . $raid[$RAIDType]->getLevel() . " data protected : "; + if ($raid[$RAIDType]->getDrivesFailureSupported() === 0) { + echo "no"; + } else if ($raid[$RAIDType]->getDrivesFailureSupported() === 1) { + echo "yes, once"; + } else if ($raid[$RAIDType]->getDrivesFailureSupported() === 2) { + echo "yes, twice"; + } else { + echo "yes, " . $raid[$RAIDType]->getDrivesFailureSupported() . " times"; + } + echo "\n\n"; } - -//var_dump($raidSHR); diff --git a/src/AbstractRaid.php b/src/AbstractRaid.php index fdc13d6..039076e 100644 --- a/src/AbstractRaid.php +++ b/src/AbstractRaid.php @@ -13,6 +13,7 @@ abstract class AbstractRaid protected $parity = false; protected $striped = false; protected $minimumDrives = 1; + protected $drivesFailureSupported = 0; /** * Is Mirrored @@ -34,6 +35,16 @@ public function getMinimumDrives() return $this->minimumDrives; } + /** + * Get Drives Failure Supported Count + * + * @return int The drives failure supported count of this RAID. + */ + public function getDrivesFailureSupported() + { + return $this->drivesFailureSupported; + } + /** * Is Striped * @@ -97,45 +108,93 @@ public function getLevel() /** * Get Drives * - * @param array $options Options - add 'withHotSpares' to include hot spare drives. + * Options: + * + * ``` + * - sortBy - Whether to sort the drives by capacity + * - sortOrder - If sorted, the order (ascending ('ASC') or descending ('DESC')), ascending by default + * - withHotSpares - Whether to include the hot spares in the drives. + * ``` + * + * @param array $options Additional options to scope the results. * @return array The RAID's Drive objects. */ public function getDrives(array $options = []) { $options += [ - 'withHotSpares' => false + 'sortBy' => null, + 'sortOrder' => 'ASC', + 'withHotSpares' => false, ]; - $drives = $this->drives; if ($options['withHotSpares'] === false) { $drives = []; + foreach ($this->drives as $drive) { if ($drive->isHotSpare() === false) { - $drives[] = $drive; + array_push($drives, $drive); } } + } else { + $drives = $this->drives; + } + + if ($options['sortBy'] === 'capacity') { + if ($options['sortOrder'] === 'ASC') { + usort($drives, function ($a, $b) { + return $a->getCapacity() - $b->getCapacity(); // sorted by ASC order + }); + } else { + usort($drives, function ($a, $b) { + return $b->getCapacity() - $a->getCapacity(); // sorted by DESC order + }); + } } return $drives; } /** - * Get Hot Spares + * Get Hot Spare Drives * - * @return array The RAID's Drive objects listed as hot spares. + * Options: + * + * ``` + * - sortBy - Whether to sort the drives by capacity + * - sortOrder - If sorted, the order (ascending ('ASC') or descending ('DESC')), ascending by default + * ``` + * + * @param array $options Additional options to scope the results. + * @return array The RAID's Drive objects listed as hot spare drives. */ - public function getHotSpares() + public function getHotSpareDrives(array $options = []) { - $hotSpares = []; - $drives = $this->getDrives(['withHotSpares' => true]); + $options += [ + 'sortBy' => null, + 'sortOrder' => 'ASC', + ]; - foreach ($drives as $drive) { + $hotSpareDrives = []; + + foreach ($this->drives as $drive) { if ($drive->isHotSpare() === true) { - $hotSpares[] = $drive; + array_push($hotSpareDrives, $drive); + } + } + + if ($options['sortBy'] === 'capacity') { + if ($options['sortOrder'] === 'ASC') { + usort($drives, function ($a, $b) { + return $a->getCapacity() - $b->getCapacity(); // sorted by ASC order + }); + } else { + usort($hotSpareDrives, function ($a, $b) { + return $b->getCapacity() - $a->getCapacity(); // sorted by DESC order + }); } } - return $hotSpares; + return $hotSpareDrives; } /** @@ -219,20 +278,20 @@ public function getMinimumDriveSize(array $options = []) 'human' => false, 'withHotSpares' => false, ]; - $floor = $this->drives[0]->getCapacity(); + $minimumDriveSize = $this->getDrives($options)[0]->getCapacity(); $drives = $this->getDrives($options); foreach ($drives as $drive) { - if ($drive->getCapacity() < $floor) { - $floor = $drive->getCapacity(); + if ($drive->getCapacity() < $minimumDriveSize) { + $minimumDriveSize = $drive->getCapacity(); } } if ($options['human'] === true) { - $floor = Number::toReadableSize($floor); + $minimumDriveSize = Number::toReadableSize($minimumDriveSize); } - return $floor; + return $minimumDriveSize; } /** @@ -256,20 +315,19 @@ public function getMaximumDriveSize(array $options = []) 'human' => false, 'withHotSpares' => false, ]; - $ceil = $this->drives[0]->getCapacity(); - $drives = $this->getDrives($options); + $maximumDriveSize = $this->drives[0]->getCapacity(); - foreach ($drives as $drive) { - if ($drive->getCapacity() > $ceil) { - $ceil = $drive->getCapacity(); + foreach ($this->getDrives($options) as $drive) { + if ($drive->getCapacity() > $maximumDriveSize) { + $maximumDriveSize = $drive->getCapacity(); } } if ($options['human'] === true) { - $ceil = Number::toReadableSize($ceil); + $maximumDriveSize = Number::toReadableSize($maximumDriveSize); } - return $ceil; + return $maximumDriveSize; } /** @@ -318,12 +376,9 @@ public function getTotalCapacity(array $options = []) 'floor' => false, ]; $total = 0; - $min = $this->getMinimumDriveSize(); - - $drives = $this->getDrives($options); - - foreach ($drives as $drive) { - $total += $options['floor'] === true ? $min : $drive->getCapacity(); + $minimumDriveSize = $this->getMinimumDriveSize(); + foreach ($this->getDrives($options) as $drive) { + $total += $options['floor'] === true ? $minimumDriveSize : $drive->getCapacity(); } if ($options['human'] === true) { @@ -399,12 +454,7 @@ public function getLossedSpace(array $options = []) 'human' => false, 'withHotSpares' => false, ]; - $lossedSpace = $this->getTotalCapacity() - $this->getCapacity() - $this->getParitySize(); - if ($options['withHotSpares'] === true) { - foreach ($this->getHotSpares() as $hotSpareDrive) { - $lossedSpace += $hotSpareDrive->getCapacity(); - } - } + $lossedSpace = $this->getTotalCapacity($options) - $this->getCapacity() - $this->getParitySize(); if ($options['human'] === true) { $lossedSpace = Number::toReadableSize($lossedSpace); @@ -423,27 +473,62 @@ public function getLossedSpace(array $options = []) public function getNumberOfDrivesOfThisCapacity($capacity, array $options = []) { $options += [ - 'withHotSpares' => false + 'withHotSpares' => false, ]; if (ctype_digit($capacity)) { - $bytes = (int)$capacity; + $capacity = (int)$capacity; + } else { + $capacity = (int)Text::parseFileSize($capacity); } - $capacity = isset($bytes) === true ? $bytes : (int)Text::parseFileSize($capacity); $numberOfDrivesOfThisCapacity = 0; - foreach ($this->drives as $drive) { + $temp = $this->getDrives($options); + foreach ($temp as $drive) { if ($drive->getCapacity() === $capacity) { ++$numberOfDrivesOfThisCapacity; } } - if ($options['withHotSpares'] === true) { - foreach ($this->getHotSpares() as $hotSpareDrive) { - if ($hotSpareDrive->getCapacity() === $capacity) { - ++$numberOfDrivesOfThisCapacity; - } + + return $numberOfDrivesOfThisCapacity; + } + + /** + * Get next maximum drive capacity + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * - withHotSpares - Whether to include the hot spares in the sum. + * ``` + * + * @param mixed $maximumDriveCapacity The capacity of the biggest drive you know, in bytes or human readable format, e.g. - '500GB', '5T', etc. + * @param array $options Additional options to scope the results. + * @return int|string The next bigger drive's capacity of the drives array, return 0 if no smaller drive than the capacity provided. + */ + public function getNextMaximumDriveCapacity($maximumDriveCapacity, array $options = []) + { + $options += [ + 'human' => false, + 'withHotSpares' => false, + ]; + if (ctype_digit($maximumDriveCapacity)) { + $maximumDriveCapacity = (int)$maximumDriveCapacity; + } else { + $maximumDriveCapacity = (int)Text::parseFileSize($maximumDriveCapacity); + } + + $capacityOfNextLargerDrive = 0; + foreach ($this->getDrives($options) as $drive) { + if ($drive->getCapacity() > $capacityOfNextLargerDrive && $drive->getCapacity() < $maximumDriveCapacity) { + $capacityOfNextLargerDrive = $drive->getCapacity(); } } - return $numberOfDrivesOfThisCapacity; + if ($options['human'] === true) { + $capacityOfNextLargerDrive = Number::toReadableSize($capacityOfNextLargerDrive); + } + + return $capacityOfNextLargerDrive; } } diff --git a/src/Drive.php b/src/Drive.php index 42a033a..7acb347 100644 --- a/src/Drive.php +++ b/src/Drive.php @@ -11,6 +11,7 @@ class Drive protected $type = null; protected $hotSpare = false; protected $identifier = null; + protected static $types = ['ssd', 'hdd']; /** * Constructor. @@ -44,6 +45,16 @@ public function __construct($capacity, string $type, string $identifier, $option $this->hotSpare = $options['hotSpare']; } + /** + * Get Types + * + * @return string All drives Types. + */ + public static function getTypes() + { + return self::$types; + } + /** * Get Identifier * @@ -78,9 +89,7 @@ public function setIdentifier(string $identifier) */ protected function validate($type) { - $types = ['ssd', 'hdd']; - - if (in_array($type, $types) === false) { + if (!in_array($type, $this->getTypes())) { throw new InvalidArgumentException('This is not a valid drive type.'); } } diff --git a/src/Raid/RaidFive.php b/src/Raid/RaidFive.php index ece6aa3..9162517 100644 --- a/src/Raid/RaidFive.php +++ b/src/Raid/RaidFive.php @@ -9,11 +9,11 @@ class RaidFive extends AbstractRaid { const LEVEL = 5; protected $drives = []; - protected $hotSpares = []; - protected $minimumDrives = 3; protected $mirrored = false; protected $parity = true; protected $striped = true; + protected $minimumDrives = 3; + protected $drivesFailureSupported = 1; /** * Constructor. diff --git a/src/Raid/RaidOne.php b/src/Raid/RaidOne.php index 0b59051..3867c0c 100644 --- a/src/Raid/RaidOne.php +++ b/src/Raid/RaidOne.php @@ -9,10 +9,11 @@ class RaidOne extends AbstractRaid { const LEVEL = 1; protected $drives = []; - protected $hotSpares = []; - protected $minimumDrives = 2; protected $mirrored = true; + protected $parity = false; protected $striped = false; + protected $minimumDrives = 2; + protected $drivesFailureSupported = 1; /** * Constructor. @@ -25,6 +26,7 @@ public function __construct(array $drives = []) $this->validate($drives); } + $this->drivesFailureSupported = count($drives) - 1; $this->setDrives($drives); } diff --git a/src/Raid/RaidSHR.php b/src/Raid/RaidSHR.php index c32dee1..d7fd1c8 100644 --- a/src/Raid/RaidSHR.php +++ b/src/Raid/RaidSHR.php @@ -9,11 +9,11 @@ class RaidSHR extends AbstractRaid { const LEVEL = "SHR"; protected $drives = []; - protected $hotSpares = []; - protected $minimumDrives = 2; protected $mirrored = false; protected $parity = true; protected $striped = true; + protected $minimumDrives = 2; + protected $drivesFailureSupported = 1; /** * Constructor. @@ -77,9 +77,8 @@ public function getParitySize(array $options = []) $options += [ 'human' => false, ]; - $minimumDriveSizeOfRAID = $this->getMinimumDriveSize(); $maximumDriveSizeOfRAID = $this->getMaximumDriveSize(); - if ($this->getNumberOfDrivesOfThisCapacity($maximumDriveSizeOfRAID) >= 2) { + if ($this->getNumberOfDrivesOfThisCapacity($maximumDriveSizeOfRAID) >= $this->minimumDrives) { $paritySize = $maximumDriveSizeOfRAID; } else { /* We are looking for the second disk with the highest capacity present in the drives array, the use of the first will be the same as the capacity of the second */ diff --git a/src/Raid/RaidSHR2.php b/src/Raid/RaidSHR2.php new file mode 100644 index 0000000..6e21a07 --- /dev/null +++ b/src/Raid/RaidSHR2.php @@ -0,0 +1,131 @@ +validate($drives); + } + + $this->setDrives($drives); + } + + /** + * Get Capacity + * + * Get the usable capacity of the RAID in its current state. + * This method differs slightly per RAID level implementation. + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to pass. + * @return int|string Usable capacity of the RAID in bytes or human readable format. + */ + public function getCapacity(array $options = []) + { + $options += [ + 'human' => false, + ]; + $numberOfParityDrives = $this->minimumDrives - 2; /* the different blocks of parities are distributed among all drives */ + $maximumDriveSizeOfRAID = $this->getMaximumDriveSize(); + if ($this->getNumberOfDrivesOfThisCapacity($maximumDriveSizeOfRAID) >= $this->minimumDrives) { + $capacity = $this->getTotalCapacity() - ($maximumDriveSizeOfRAID * $numberOfParityDrives); + } else { + /* We are looking for the second drive with the highest capacity present in the drives array, the use of the first will be the same as the capacity of the second */ + $capacity = 0; + $capacityOfSecondLargestDrive = $this->getNextMaximumDriveCapacity($maximumDriveSizeOfRAID); + if ($this->getNumberOfDrivesOfThisCapacity($capacityOfSecondLargestDrive) >= $numberOfParityDrives) { + foreach ($this->drives as $drive) { + if ($drive->getCapacity() < $capacityOfSecondLargestDrive) { + $capacity += $drive->getCapacity(); + } else { + $capacity += $capacityOfSecondLargestDrive; + } + } + $capacity -= $capacityOfSecondLargestDrive * $numberOfParityDrives; + } else { + /* We are looking for the third drive with the highest capacity present in the drives array, the use of the two firsts will be the same as the capacity of the third */ + $capacityOfThirdLargestDrive = $this->getNextMaximumDriveCapacity($capacityOfSecondLargestDrive); + foreach ($this->drives as $drive) { + if ($drive->getCapacity() < $capacityOfThirdLargestDrive) { + $capacity += $drive->getCapacity(); + } else { + $capacity += $capacityOfThirdLargestDrive; + } + } + $capacity -= $capacityOfThirdLargestDrive * $numberOfParityDrives; + } + } + + if ($options['human'] === true) { + return Number::toReadableSize($capacity); + } + + return $capacity; + } + + /** + * Get parity total size + * + * Get the total size reserved for parity (unusable by data but not lossed). + * + * Options: + * + * ``` + * - human - Whether to convert the result into human readable units, e.g. - 4 TB, 500 GB, etc + * ``` + * + * @param array $options Additional options to scope the results. + * @return int|string The total size reserved for parity of the RAID. + */ + public function getParitySize(array $options = []) + { + $options += [ + 'human' => false, + ]; + $numberOfParityDrives = $this->minimumDrives - 2; /* the different blocks of parities are distributed among all drives */ + $maximumDriveSizeOfRAID = $this->getMaximumDriveSize(); + if ($this->getNumberOfDrivesOfThisCapacity($maximumDriveSizeOfRAID) >= $this->minimumDrives) { + $paritySize = $maximumDriveSizeOfRAID * $numberOfParityDrives; + } else { + /* We are looking for the second drive with the highest capacity present in the drives array, the use of the first will be the same as the capacity of the second */ + $capacityOfSecondLargestDrive = $this->getNextMaximumDriveCapacity($maximumDriveSizeOfRAID); + if ($this->getNumberOfDrivesOfThisCapacity($capacityOfSecondLargestDrive) >= $numberOfParityDrives) { + $paritySize = $capacityOfSecondLargestDrive * $numberOfParityDrives; + } else { + /* We are looking for the third drive with the highest capacity present in the drives array, the use of the two firsts will be the same as the capacity of the third */ + $capacityOfThirdLargestDrive = $this->getNextMaximumDriveCapacity($capacityOfSecondLargestDrive); + $paritySize = $capacityOfThirdLargestDrive * $numberOfParityDrives; + } + } + + if ($options['human'] === true) { + return Number::toReadableSize($paritySize); + } + + return $paritySize; + } +} diff --git a/src/Raid/RaidSix.php b/src/Raid/RaidSix.php index 80ec0de..354384f 100644 --- a/src/Raid/RaidSix.php +++ b/src/Raid/RaidSix.php @@ -9,11 +9,11 @@ class RaidSix extends AbstractRaid { const LEVEL = 6; protected $drives = []; - protected $hotSpares = []; - protected $minimumDrives = 4; protected $mirrored = false; protected $parity = true; protected $striped = true; + protected $minimumDrives = 4; + protected $drivesFailureSupported = 2; /** * Constructor. diff --git a/src/Raid/RaidTen.php b/src/Raid/RaidTen.php index abb4686..6f57e42 100644 --- a/src/Raid/RaidTen.php +++ b/src/Raid/RaidTen.php @@ -9,9 +9,11 @@ class RaidTen extends AbstractRaid { const LEVEL = 10; protected $drives = []; - protected $hotSpares = []; - protected $minimumDrives = 4; protected $mirrored = true; + protected $parity = false; + protected $striped = true; + protected $minimumDrives = 4; + protected $drivesFailureSupported = 2; /** * Constructor. @@ -23,6 +25,8 @@ public function __construct(array $drives = []) if (empty($drives) === false) { $this->validate($drives); } + + $this->drivesFailureSupported = count($drives) / 2; $this->setDrives($drives); } @@ -42,7 +46,14 @@ public function getCapacity(array $options = []) $options += [ 'human' => false, ]; - $capacity = $this->getTotalCapacity() / 2; + $drivesOrderedByCapacity = $this->getDrives(['orderBy' => 'capacity', 'sortOrder' => 'DESC']); + $numberOfDrives = count($drivesOrderedByCapacity); + $capacity = 0; + for ($i = 0; $i + 1 < $numberOfDrives; $i += 2) { + $firstDrive = $drivesOrderedByCapacity[$i]; + $secondDrive = $drivesOrderedByCapacity[$i + 1]; + $capacity += min($firstDrive->getCapacity(), $secondDrive->getCapacity()); + } if ($options['human'] === true) { return Number::toReadableSize($capacity); @@ -70,7 +81,14 @@ public function getParitySize(array $options = []) $options += [ 'human' => false, ]; - $paritySize = $this->getTotalCapacity() / 2; + $drivesOrderedByCapacity = $this->getDrives(['orderBy' => 'capacity', 'sortOrder' => 'DESC']); + $numberOfDrives = count($drivesOrderedByCapacity); + $paritySize = 0; + for ($i = 0; $i + 1 < $numberOfDrives; $i += 2) { + $firstDrive = $drivesOrderedByCapacity[$i]; + $secondDrive = $drivesOrderedByCapacity[$i + 1]; + $paritySize += min($firstDrive->getCapacity(), $secondDrive->getCapacity()); + } if ($options['human'] === true) { return Number::toReadableSize($paritySize); diff --git a/src/Raid/RaidZero.php b/src/Raid/RaidZero.php index d5a366a..bf5ab6b 100644 --- a/src/Raid/RaidZero.php +++ b/src/Raid/RaidZero.php @@ -9,10 +9,11 @@ class RaidZero extends AbstractRaid { const LEVEL = 0; protected $drives = []; - protected $hotSpares = []; - protected $minimumDrives = 2; protected $mirrored = false; + protected $parity = false; protected $striped = true; + protected $minimumDrives = 2; + protected $drivesFailureSupported = 0; /** * Constructor. diff --git a/src/RaidFactory.php b/src/RaidFactory.php index a8417cf..733e944 100644 --- a/src/RaidFactory.php +++ b/src/RaidFactory.php @@ -5,6 +5,7 @@ use kevinquinnyo\Raid\Raid\RaidFive; use kevinquinnyo\Raid\Raid\RaidOne; use kevinquinnyo\Raid\Raid\RaidSHR; +use kevinquinnyo\Raid\Raid\RaidSHR2; use kevinquinnyo\Raid\Raid\RaidSix; use kevinquinnyo\Raid\Raid\RaidTen; use kevinquinnyo\Raid\Raid\RaidZero; @@ -43,6 +44,9 @@ public function create($level, $drives) case $level === 'SHR': $raid = new RaidSHR($drives); break; + case $level === 'SHR2': + $raid = new RaidSHR2($drives); + break; } if ($raid === null) { diff --git a/tests/AbstractRaidTest.php b/tests/AbstractRaidTest.php index ff4febf..e944fc3 100644 --- a/tests/AbstractRaidTest.php +++ b/tests/AbstractRaidTest.php @@ -51,11 +51,11 @@ public function testValidateThrowsExceptionWhenDriveIdentifierAlreadyPresent() $raidClass->validate($existingDrive); } - public function testGetHotSpares() + public function testGetHotSpareDrives() { $concreteRaid = $this->getMockForAbstractClass(AbstractRaid::class); $raidClass = new $concreteRaid(); - $this->assertEquals([], $raidClass->getHotSpares()); + $this->assertEquals([], $raidClass->getHotSpareDrives()); } public function testAddHotSpare() @@ -64,12 +64,12 @@ public function testAddHotSpare() $raidClass = new $concreteRaid(); // ensure there are no hot spares to begin with. - $this->assertEquals([], $raidClass->getHotSpares()); + $this->assertEquals([], $raidClass->getHotSpareDrives()); $newDrive = new Drive(1024, 'ssd', 1); $raidClass = $raidClass->addHotSpare($newDrive); - $this->assertEquals([$newDrive], $raidClass->getHotSpares()); + $this->assertEquals([$newDrive], $raidClass->getHotSpareDrives()); } public function testGetDriveCount() @@ -256,8 +256,33 @@ public function testGetMinimumDriveSizeWithHotSpare() $concreteRaid = $this->getMockForAbstractClass(AbstractRaid::class); $raidClass = new $concreteRaid(); $raidClass->setDrives($drives); + $this->assertSame(1024, $raidClass->getMinimumDriveSize(['withHotSpares' => true])); + $this->assertSame(1024, $raidClass->getMinimumDriveSize(['withHotSpares' => false])); + $raidClass->addHotSpare($hotSpare); $this->assertSame(512, $raidClass->getMinimumDriveSize(['withHotSpares' => true])); + $this->assertSame(1024, $raidClass->getMinimumDriveSize(['withHotSpares' => false])); + } + + public function testGetMaximumDriveSizeWithHotSpare() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(2048, 'ssd', 2), + new Drive(2048, 'ssd', 3), + ]; + + $hotSpare = new Drive(4096, 'ssd', 4); + + $concreteRaid = $this->getMockForAbstractClass(AbstractRaid::class); + $raidClass = new $concreteRaid(); + $raidClass->setDrives($drives); + $this->assertSame(2048, $raidClass->getMaximumDriveSize(['withHotSpares' => false])); + $this->assertSame(2048, $raidClass->getMaximumDriveSize(['withHotSpares' => true])); + + $raidClass->addHotSpare($hotSpare); + $this->assertSame(2048, $raidClass->getMaximumDriveSize(['withHotSpares' => false])); + $this->assertSame(4096, $raidClass->getMaximumDriveSize(['withHotSpares' => true])); } public function testAddDrive() @@ -331,4 +356,53 @@ public function testValidDriveCountWithEvenDrivesPlusSpare() $raidClass->setDrives($drives); $this->assertTrue($raidClass->validDriveCount()); } + + public function testGetNumberOfDrivesOfThisCapacity() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(2048, 'ssd', 2), + new Drive(2048, 'ssd', 3), + new Drive(2048, 'ssd', 4), + new Drive(2048, 'ssd', 5, ['hotSpare' => true]), + ]; + + $concreteRaid = $this->getMockForAbstractClass(AbstractRaid::class); + $raidClass = new $concreteRaid(); + /* Basically this is a Raid 10 with 4 drives and one hot spare */ + $this->setProtectedProperty($raidClass, 'minimumDrives', 4); + $this->setProtectedProperty($raidClass, 'mirrored', true); + + $raidClass->setDrives($drives); + $this->assertSame(1, $raidClass->getNumberOfDrivesOfThisCapacity(1024, array('withHotSpares' => true))); + $this->assertSame(1, $raidClass->getNumberOfDrivesOfThisCapacity(1024, array('withHotSpares' => false))); + $this->assertSame(3, $raidClass->getNumberOfDrivesOfThisCapacity(2048, array('withHotSpares' => false))); + $this->assertSame(4, $raidClass->getNumberOfDrivesOfThisCapacity(2048, array('withHotSpares' => true))); + } + + public function testGetNextMaximumDriveCapacity() + { + $drives = [ + new Drive(1024, 'ssd', 1, ['hotSpare' => true]), + new Drive(2048, 'ssd', 2), + new Drive(2048, 'ssd', 3), + new Drive(4096, 'ssd', 4, ['hotSpare' => true]), + ]; + + $concreteRaid = $this->getMockForAbstractClass(AbstractRaid::class); + $raidClass = new $concreteRaid(); + /* Basically this is a Raid 10 with 4 drives and one hot spare */ + $this->setProtectedProperty($raidClass, 'minimumDrives', 4); + $this->setProtectedProperty($raidClass, 'mirrored', true); + + $raidClass->setDrives($drives); + $this->assertSame(0, $raidClass->getNextMaximumDriveCapacity(1024, array('withHotSpares' => true))); + $this->assertSame(0, $raidClass->getNextMaximumDriveCapacity(1024, array('withHotSpares' => false))); + $this->assertSame(1024, $raidClass->getNextMaximumDriveCapacity(2048, array('withHotSpares' => true))); + $this->assertSame(0, $raidClass->getNextMaximumDriveCapacity(2048, array('withHotSpares' => false))); + $this->assertSame(2048, $raidClass->getNextMaximumDriveCapacity(4096, array('withHotSpares' => true))); + $this->assertSame(2048, $raidClass->getNextMaximumDriveCapacity(4096, array('withHotSpares' => false))); + $this->assertSame(4096, $raidClass->getNextMaximumDriveCapacity(8192, array('withHotSpares' => true))); + $this->assertSame(2048, $raidClass->getNextMaximumDriveCapacity(8192, array('withHotSpares' => false))); + } } diff --git a/tests/Raid/RaidFiveTest.php b/tests/Raid/RaidFiveTest.php index dec9c0d..8520255 100644 --- a/tests/Raid/RaidFiveTest.php +++ b/tests/Raid/RaidFiveTest.php @@ -30,10 +30,39 @@ public function testGetCapacityWithHotSparesWithoutHotSparesOption() ]; $raidFive = new RaidFive($drives); $this->assertSame(2048, $raidFive->getCapacity()); + $this->assertSame("2 KB", $raidFive->getCapacity(['human' => true])); } + public function testGetLevel() { $raidFive = new RaidFive(); $this->assertSame(5, $raidFive->getLevel()); } + + public function testGetParitySize() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + ]; + $raidFive = new RaidFive($drives); + $this->assertSame(1024, $raidFive->getParitySize()); + $this->assertSame("1 KB", $raidFive->getParitySize(['human' => true])); + } + + public function testGetParitySizeWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(1024, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidFive = new RaidFive($drives); + $this->assertSame(1024, $raidFive->getParitySize()); + $this->assertSame("1 KB", $raidFive->getParitySize(['human' => true])); + } } diff --git a/tests/Raid/RaidOneTest.php b/tests/Raid/RaidOneTest.php index 6b1beb8..45292ae 100644 --- a/tests/Raid/RaidOneTest.php +++ b/tests/Raid/RaidOneTest.php @@ -17,6 +17,7 @@ public function testGetCapacity() $this->assertSame(1024, $raidOne->getCapacity()); $this->assertSame('1 KB', $raidOne->getCapacity(['human' => true])); } + public function testGetCapacityWithHotSpares() { $drives = [ @@ -28,10 +29,39 @@ public function testGetCapacityWithHotSpares() ]; $raidOne = new RaidOne($drives); $this->assertSame(1024, $raidOne->getCapacity()); + $this->assertSame('1 KB', $raidOne->getCapacity(['human' => true])); } + public function testGetLevel() { $raidOne = new RaidOne(); $this->assertSame(1, $raidOne->getLevel()); } + + public function testGetParitySize() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + ]; + $raidOne = new RaidOne($drives); + $this->assertSame(2048, $raidOne->getParitySize()); + $this->assertSame("2 KB", $raidOne->getParitySize(['human' => true])); + } + + public function testGetParitySizeWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(1024, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidOne = new RaidOne($drives); + $this->assertSame(3072, $raidOne->getParitySize()); + $this->assertSame("3 KB", $raidOne->getParitySize(['human' => true])); + } } diff --git a/tests/Raid/RaidSHR2Test.php b/tests/Raid/RaidSHR2Test.php new file mode 100644 index 0000000..a5b9e62 --- /dev/null +++ b/tests/Raid/RaidSHR2Test.php @@ -0,0 +1,71 @@ +assertSame(2048, $raidSHR2->getCapacity()); + $this->assertSame('2 KB', $raidSHR2->getCapacity(['human' => true])); + } + + public function testGetCapacityWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(1024, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidSHR2 = new RaidSHR2($drives); + $this->assertSame(2048, $raidSHR2->getCapacity()); + $this->assertSame("2 KB", $raidSHR2->getCapacity(['human' => true])); + } + + public function testGetLevel() + { + $raidSHR2 = new RaidSHR2(); + $this->assertSame('SHR2', $raidSHR2->getLevel()); + } + + public function testGetParitySize() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + ]; + $raidSHR2 = new RaidSHR2($drives); + $this->assertSame(2048, $raidSHR2->getParitySize()); + $this->assertSame("2 KB", $raidSHR2->getParitySize(['human' => true])); + } + + public function testGetParitySizeWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(1024, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidSHR2 = new RaidSHR2($drives); + $this->assertSame(2048, $raidSHR2->getParitySize()); + $this->assertSame("2 KB", $raidSHR2->getParitySize(['human' => true])); + } +} diff --git a/tests/Raid/RaidSHRTest.php b/tests/Raid/RaidSHRTest.php index cbfc7e0..bfddbec 100644 --- a/tests/Raid/RaidSHRTest.php +++ b/tests/Raid/RaidSHRTest.php @@ -18,6 +18,7 @@ public function testGetCapacity() $this->assertSame(2048, $raidSHR->getCapacity()); $this->assertSame('2 KB', $raidSHR->getCapacity(['human' => true])); } + public function testGetCapacityWithHotSpares() { $drives = [ @@ -29,22 +30,39 @@ public function testGetCapacityWithHotSpares() ]; $raidSHR = new RaidSHR($drives); $this->assertSame(2048, $raidSHR->getCapacity()); + $this->assertSame("2 KB", $raidSHR->getCapacity(['human' => true])); } + public function testGetLevel() { $raidSHR = new RaidSHR(); $this->assertSame('SHR', $raidSHR->getLevel()); } + public function testGetParitySize() { $drives = [ new Drive(1024, 'ssd', 1), new Drive(1024, 'ssd', 2), new Drive(1024, 'ssd', 3), - new Drive(1024, 'ssd', 4, ['hotSpare' => true]), + ]; + $raidSHR = new RaidSHR($drives); + $this->assertSame(1024, $raidSHR->getParitySize()); + $this->assertSame("1 KB", $raidSHR->getParitySize(['human' => true])); + } + + public function testGetParitySizeWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(1024, 'ssd', 6, ['hotSpare' => true]), ]; $raidSHR = new RaidSHR($drives); $this->assertSame(1024, $raidSHR->getParitySize()); + $this->assertSame("1 KB", $raidSHR->getParitySize(['human' => true])); } } diff --git a/tests/Raid/RaidSixTest.php b/tests/Raid/RaidSixTest.php index 9be8900..390fc8e 100644 --- a/tests/Raid/RaidSixTest.php +++ b/tests/Raid/RaidSixTest.php @@ -31,10 +31,38 @@ public function testGetCapacityWithHotSpares() ]; $raidSix = new RaidSix($drives); $this->assertSame(2048, $raidSix->getCapacity()); + $this->assertSame("2 KB", $raidSix->getCapacity(['human' => true])); } public function testGetLevel() { $raidSix = new RaidSix(); $this->assertSame(6, $raidSix->getLevel()); } + + public function testGetParitySize() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + ]; + $raidSix = new RaidSix($drives); + $this->assertSame(2048, $raidSix->getParitySize()); + $this->assertSame("2 KB", $raidSix->getParitySize(['human' => true])); + } + + public function testGetParitySizeWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(1024, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidSix = new RaidSix($drives); + $this->assertSame(2048, $raidSix->getParitySize()); + $this->assertSame("2 KB", $raidSix->getParitySize(['human' => true])); + } } diff --git a/tests/Raid/RaidTenTest.php b/tests/Raid/RaidTenTest.php index 73f5d81..3b84d0e 100644 --- a/tests/Raid/RaidTenTest.php +++ b/tests/Raid/RaidTenTest.php @@ -13,15 +13,59 @@ public function testGetCapacity() new Drive(1024, 'ssd', 1), new Drive(1024, 'ssd', 2), new Drive(1024, 'ssd', 3), - new Drive(1024, 'ssd', 4), + new Drive(2048, 'ssd', 4), ]; $raidTen = new RaidTen($drives); $this->assertSame(2048, $raidTen->getCapacity()); $this->assertSame('2 KB', $raidTen->getCapacity(['human' => true])); } + + public function testGetCapacityWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(2048, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(2048, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidTen = new RaidTen($drives); + $this->assertSame(2048, $raidTen->getCapacity()); + $this->assertSame('2 KB', $raidTen->getCapacity(['human' => true])); + } + public function testGetLevel() { $raidTen = new RaidTen(); $this->assertSame(10, $raidTen->getLevel()); } + + public function testGetParitySize() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(2048, 'ssd', 4), + ]; + $raidTen = new RaidTen($drives); + $this->assertSame(2048, $raidTen->getParitySize()); + $this->assertSame("2 KB", $raidTen->getParitySize(['human' => true])); + } + + public function testGetParitySizeWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(2048, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(2048, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidTen = new RaidTen($drives); + $this->assertSame(2048, $raidTen->getParitySize()); + $this->assertSame("2 KB", $raidTen->getParitySize(['human' => true])); + } } diff --git a/tests/Raid/RaidZeroTest.php b/tests/Raid/RaidZeroTest.php index 55fa23f..141cd42 100644 --- a/tests/Raid/RaidZeroTest.php +++ b/tests/Raid/RaidZeroTest.php @@ -29,6 +29,7 @@ public function testGetCapacityWithHotSpares() ]; $raidZero = new RaidZero($drives); $this->assertSame(3072, $raidZero->getCapacity()); + $this->assertSame("3 KB", $raidZero->getCapacity(['human' => true])); } public function testGetLevel() @@ -36,4 +37,31 @@ public function testGetLevel() $raidZero = new RaidZero(); $this->assertSame(0, $raidZero->getLevel()); } + + public function testGetParitySize() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + ]; + $raidZero = new RaidZero($drives); + $this->assertSame(0, $raidZero->getParitySize()); + $this->assertSame("0 Byte", $raidZero->getParitySize(['human' => true])); + } + + public function testGetParitySizeWithHotSpares() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + new Drive(1024, 'ssd', 5, ['hotSpare' => true]), + new Drive(1024, 'ssd', 6, ['hotSpare' => true]), + ]; + $raidZero = new RaidZero($drives); + $this->assertSame(0, $raidZero->getParitySize()); + $this->assertSame("0 Byte", $raidZero->getParitySize(['human' => true])); + } } diff --git a/tests/RaidFactoryTest.php b/tests/RaidFactoryTest.php index b2ddce0..9a7af31 100644 --- a/tests/RaidFactoryTest.php +++ b/tests/RaidFactoryTest.php @@ -7,9 +7,10 @@ use \kevinquinnyo\Raid\Raid\RaidZero; use \kevinquinnyo\Raid\Raid\RaidOne; use \kevinquinnyo\Raid\Raid\RaidFive; +use \kevinquinnyo\Raid\Raid\RaidSHR; +use \kevinquinnyo\Raid\Raid\RaidSHR2; use \kevinquinnyo\Raid\Raid\RaidSix; use \kevinquinnyo\Raid\Raid\RaidTen; -use \kevinquinnyo\Raid\Raid\RaidSHR; use InvalidArgumentException; class RaidFactoryTest extends TestCase @@ -84,4 +85,16 @@ public function testCreateSHRRaid() $raid = $factory->create('SHR', $drives); $this->assertInstanceOf(RaidSHR::class, $raid); } + public function testCreateSHR2Raid() + { + $drives = [ + new Drive(1024, 'ssd', 1), + new Drive(1024, 'ssd', 2), + new Drive(1024, 'ssd', 3), + new Drive(1024, 'ssd', 4), + ]; + $factory = new RaidFactory(); + $raid = $factory->create('SHR2', $drives); + $this->assertInstanceOf(RaidSHR2::class, $raid); + } }