* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /** * @class OMVModuleZPool. */ require ('OMVModulePoolType.php'); require ('OMVModulePoolAction.php'); class OMVModuleZPool { private $params; private $log; private $mirror_log; private $cache; private $spare; private $alloc; private $status; // one of null, ok, degraded, faulted, scrub, resilver private $debug; /** * Constructor of class OMVModuleZPool. * @param params Array which can contain all or a subset of * the following fields: * * @param options Array which can contain all or a subset of * the following fields: * * @param debug Print debug information to STDERR. * @throws Exception. */ public function __construct(array $params = array(), array $options = array(), $debug = false) { $this->params = array( 'atime' => 'on', 'compress' => 'off', 'dedub' => 'off', 'name' => '', 'size' => '0M', 'sync' => 'off', 'type' => OMVModulePoolType::OMVModulePoolType_TYPE_NONE, 'vdevs' => 1 ); $this->debug = $debug; foreach ($params as $key => $value) { if (array_key_exists($key, $this->params)) { $this->validate($key, $value); $this->params[$key] = $value; } else { throw new Exception("$key: Not valid parameter"); } } foreach ($options as $key => $value) { $this->validate($key, $value, true); } $this->status = null; if ($this->debug) { fprintf(STDERR, "OMVModuleZPool instantiate with the following params\n"); foreach ($this->params as $key => $value) { $param = ($key == 'type') ? OMVModulePoolType::toString($value) : $value; fprintf(STDERR, " %-8s => %s\n", $key, $param); } } } /** * Create pool. * @param disks Array of disks. Format: /dev/disk/by-id/... * @param name Name for the pool. * @param options Array which can contain all or a subset of * the following fields: * * @throws Exception. */ public function createPool(array $disks, $name = null, array $options = array()) { return $this->updatePool(OMVModulePoolAction::OMVModulePoolAction_TYPE_CREATE, $disks, count($disks), $name, $options); } /** * Add pool. * @param disks Array of disks. Format: /dev/disk/by-id/... * @param name Name for the pool. * @param options Array which can contain all or a subset of * the following fields: * * @throws Exception. */ public function addPool(array $disks, $name = null, array $options = array()) { $this->initPool($name); return $this->updatePool(OMVModulePoolAction::OMVModulePoolAction_TYPE_ADD, $disks, count($disks), $name, $options); } public function removeDevice(array $disks, $name = null) { } public function destroyPool($name = null) { } public function scrubPool($name = null) { } public function exportPool($name = null){ } public function importPool($name = null){ } /* * public function attachDevice($extingDisk, $newDisk, $name = null) * public function detachDevice($disk, $name = null) * public function onlineDevice($disk, $name = null) * public function offlineDevice($disk, $name = null) * public function replaceDevice($extingDisk, $newDisk, $name = null) * public function upgradePool($name = null) */ private function initPool($name = null) { // Replace with OMVUtil::exec $this->validateName($name); } private function validateName($name = null) { $oldname = null; if ($name != null) { $oldname = $this->params['name']; $this->params['name'] = $name; } if (! $this->params['name'] || ! preg_match('/^\w+$/', $this->params['name'])) { if ($oldname) { $this->params['name'] = $oldname; } throw new Exception("Name of pool cannot be null or empty"); } } /** * Update pool. * @param action Action to be taken. create or add. * @param disks Array of disks. Format: /dev/disk/by-id/... * @param disk_num Number of disk(s) in the pool. * @param name Name for the pool. * @param options Array which can contain all or a subset of * the following fields: * * @throws Exception. */ private function updatePool($action, array $disks, $disk_num, $name = null, array $options = array()) { OMVModulePoolAction::toString($action); $cmd = 'zpool ' . OMVModulePoolAction::getAction($action); if ($disk_num < 1) { throw new Exception("Pool must have at least 1 disk"); }; $this->validateName($name); foreach ($options as $key => $value) { $this->validate($key, $value, true); } switch ($action) { case OMVModulePoolAction::OMVModulePoolAction_TYPE_CREATE: $cmd .= ' ' . $this->params['name']; break; case OMVModulePoolAction::OMVModulePoolAction_TYPE_ADD: $cmd .= ' ' . $this->params['name']; break; } switch ($this->params['type']) { case OMVModulePoolType::OMVModulePoolType_TYPE_NONE: print "Create basic pool\n"; if ($this->params['vdevs'] != 1) { throw new Exception("A basic zpool can only have 1 vdev"); } $vdev = ''; foreach ($disks as $disk) { $vdev .= ($vdev) ? " $disk" : "$disk"; } $cmd .= " $vdev"; break; case OMVModulePoolType::OMVModulePoolType_TYPE_MIRROR: print "Create mirrored pool\n"; if ($disk_num / $this->params['vdevs'] < 2 || ($disk_num / $this->params['vdevs']) % $this->params['vdevs']) { throw new Exception("$disk_num disk(s) cannot be evenly distributed to ". $this->params['vdevs']." vdev(s) and form a proper stripe or mirror"); } $vdevs = array(); $disk_sum = $disk_num / $this->params['vdevs']; $s = ''; for ($i = 1, $num = 0; $i <= $disk_num; $i++) { $s .= ($s) ? " ${disks[$i-1]}" : "${disks[$i-1]}"; if ($i % $disk_sum == 0) { $vdevs[$num++] = $s; $s = ''; } } foreach ($vdevs as $vdev) { $cmd .= " mirror $vdev"; } break; case OMVModulePoolType::OMVModulePoolType_TYPE_RAIDZ1: print "Create raidz1 pool\n"; if ($disk_num / $this->params['vdevs'] < 3 || ($disk_num / $this->params['vdevs']) % $this->params['vdevs']) { throw new Exception("$disk_num disk and ".$this->params['vdevs']. " vdev(s) cannot provide mininum 3 disks per vdev for raidz1"); } $vdevs = array(); $disk_sum = $disk_num / $this->params['vdevs']; $s = ''; for ($i = 1, $num = 0; $i <= $disk_num; $i++) { $s .= ($s) ? " ${disks[$i-1]}" : "${disks[$i-1]}"; if ($i % $disk_sum == 0) { $vdevs[$num++] = $s; $s = ''; } } foreach ($vdevs as $vdev) { $cmd .= " raidz1 $vdev"; } break; case OMVModulePoolType::OMVModulePoolType_TYPE_RAIDZ2: print "Create raidz2 pool\n"; if ($disk_num / $this->params['vdevs'] < 4 || ($disk_num / $this->params['vdevs']) % $this->params['vdevs']) { throw new Exception("$disk_num disk and ".$this->params['vdevs']. " vdev(s) cannot provide mininum 4 disks per vdev for raidz2"); } $vdevs = array(); $disk_sum = $disk_num / $this->params['vdevs']; $s = ''; for ($i = 1, $num = 0; $i <= $disk_num; $i++) { $s .= ($s) ? " ${disks[$i-1]}" : "${disks[$i-1]}"; if ($i % $disk_sum == 0) { $vdevs[$num++] = $s; $s = ''; } } foreach ($vdevs as $vdev) { $cmd .= " raidz2 $vdev"; } break; case OMVModulePoolType::OMVModulePoolType_TYPE_RAIDZ3: print "Create raidz3 pool\n"; if ($disk_num / $this->params['vdevs'] < 5 || ($disk_num / $this->params['vdevs']) % $this->params['vdevs']) { throw new Exception("$disk_num disk and ".$this->params['vdevs']. " vdev(s) cannot provide mininum 5 disks per vdev for raidz3"); } $vdevs = array(); $disk_sum = $disk_num / $this->params['vdevs']; $s = ''; for ($i = 1, $num = 0; $i <= $disk_num; $i++) { $s .= ($s) ? " ${disks[$i-1]}" : "${disks[$i-1]}"; if ($i % $disk_sum == 0) { $vdevs[$num++] = $s; $s = ''; } } foreach ($vdevs as $vdev) { $cmd .= " raidz3 $vdev"; } break; } foreach ($options as $key => $value) { switch ($key) { case 'log': $cmd .= " log"; if ($value['mirror'] === true) { $cmd .= " mirror"; } foreach ($value['disks'] as $disk) { $cmd .= " $disk"; } break; case 'cache': $cmd .= " cache"; foreach ($value as $disk) { $cmd .= " $disk"; } break; case 'spare': $cmd .= " spare"; foreach ($value as $disk) { $cmd .= " $disk"; } break; } } if ($this->debug) { fprintf(STDERR, "%s\n", $cmd); } return $cmd; } /** * Validate options. * @param key Option to validate. * @param value Value for option to validate. * @param type See OMVModulePoolType. * @throws Exception if option is invalid. */ private function validate($key, $value, $options = false) { if ($options) { switch ($key) { case 'log': if (! is_array($value) || ! array_key_exists('mirror', $value) || ! array_key_exists('disks', $value)) { throw new Exception("log option missing disks array or mirror value"); } if ($value['mirror'] === true && count($value['disks']) != 2) { throw new Exception("Two disks is required to form a log mirror"); } $this->log = $value['disks']; $this->mirror_log = $value['mirror']; break; case 'cache': if (! is_array($value)) { throw new Exception("log option missing disks array or mirror value"); } $this->cache = $value; break; case 'spare': if (! is_array($value)) { throw new Exception("log option missing disks array or mirror value"); } $this->spare = $value; break; default: throw new Exception("$key: Not valid option"); } } else { switch ($key) { case 'atime': case 'compress': case 'dedub': case 'sync': if ($value != 'on' && $value != 'off') { throw new Exception("$key: Value must be 'on' or 'off' found '$value'"); } break; case 'size': if (! preg_match('/^\d+(M|G)$/', $value)) { throw new Exception("$key: Value must have format '100M' or '100G' found '$value'"); } break; case 'type': if (OMVModulePoolType::OMVModulePoolType_TYPE_NONE > $value || OMVModulePoolType::OMVModulePoolType_TYPE_RAIDZ3 < $value) { throw new Exception("$key: Value must be type of OMVPoolType found '$value'"); } break; case 'vdevs': if (! preg_match('/^\d+$/', $value) || $value < 1) { throw new Exception("$key: Value must be positive int found '$value'"); } } } } }