gryu Цитата: Толи временно, толи совсем.
ну скрипт в
cache google ещё присутствует
[more=graid5-expand]
#!/usr/local/bin/php
<?php
/**
*** GEOM RAID 5 (graid5)
*** Offline Capacity Expansion script
*** purpose: adding disks to an existing graid5-volume while preserving all data on the volume
***
*** script is copyright (C) 2007-2008, Fluffles.net (http://www.fluffles.net)
*** graid5 is copyright (C) 2006-2007, Arne Woerner (arne_woerner@yahoo.com)
***
*** This script is licensed to you under the BSD license
*** If you benefit from this script or have questions please email the author at
*** info@fluffles.net
**/
/**
*** ATTENTION:
*** o this script is still beta quality, needs testing!
*** o will not work with older versions of geom_raid5
*** o you will need sources in /usr/src
*** o will not work on FreeBSD 7.x, needs 6.x
**/
/**
*** Tunables: feel free to change these
*** tmp_dir is the temporary directory, it needs to be writable and have 1MB free space or so
*** debug will not actually perform dangerous commands, it will only display the commands to be executed.
*** stepbystep is also used for debugging purposes, it will pause after each step
*** verbose will display the commands as they are executed
**/
// temporary directory - should be writable!
$tmp_dir = '/tmp/';
// transfer x MiB's per second, do not set higher than 50% of memory size!
$datamapping_chunksize = 100*1024*1024; // 100MiB
// debugging options or verbose output
$debug = false;
$debug_calc = false;
$stepbystep = false;
$verbose = false;
// Calculator to use
// Either set to "auto" for autoselection or specify explicitly:
// "internal" - use internal PHP calculator (default for amd64 arch)
// "calc" - use external calc calculator (default for i386 arch)
// "bc" - unsupported
$calculator = 'auto';
/**
*** Initialization
*** WARNING: You are adviced not to change any of these unless you know what you are doing.
**/
$version = 3;
$version_date = 20080720;
$nl = chr(10);
$path_graid5_module = '/boot/kernel/geom_raid5.ko';
$path_graid5_lib = '/lib/geom/geom_raid5.so';
$default_sector_size = 512;
$math_scale_range = 0;
$minimum_date = mktime(0,0,0,02,18,2007); /* format: MM,DD,YYYY */
$min_free_space = 1024 * 1024;
$min_bsd_version = 6.0;
$max_bsd_version = 6.3;
$required_executables = array(
'/sbin/graid5',
'/sbin/kldstat',
'/sbin/gnop',
'/bin/dd',
'/sbin/mount',
'/sbin/sysctl',
'/usr/sbin/diskinfo');
$required_files = array(
'/boot/kernel/geom_raid5.ko',
'/boot/kernel/geom_nop.ko');
/**
*** Write banner
**/
echo($nl.'**'.$nl);
echo('*** GEOM RAID5 - Offline Capacity Expansion, version '.$version.' ('.$version_date.')'.$nl);
echo('***'.$nl);
echo('*** This script is: copyright (C) 2007-2008, Fluffles.net'.$nl);
echo('*** geom_raid5 is: copyright (C) 2006-2007, Arne Woerner'.$nl);
echo('*** License of this script: BSD'.$nl);
echo('*** For questions or help with expansion, please email to info@fluffles.net'.$nl);
echo('**'.$nl);
/**
*** Validate input
**/
// parse the command line variables
$graid5_volume = $argv[1];
$disks_to_be_added = array();
for ($i = 2; $i < 99; $i++)
if (strlen($argv[$i]) > 0)
{
$disks_to_be_added[] = $argv[$i];
$devpath = '/dev/'.$argv[$i];
if (!file_exists($devpath))
error('Could not find device "'.$devpath.'"');
if (!is_writable($devpath))
error('Could not open device "'.$devpath.'" for writing - maybe it is in use?');
}
// validate user input
if (!$graid5_volume)
usage();
if (!$disks_to_be_added)
usage();
/**
*** CHECK FREEBSD VERSION
**/
$bsdversion = trim(getcommand('sysctl -n kern.osrelease'));
echo($nl.'** Detected FreeBSD version '.$bsdversion.$nl);
$bsdversion_int = (int)substr($bsdversion,0,3);
if (($bsdversion_int < $min_bsd_version) OR ($bsdversion_int > $max_bsd_version))
error('You must be using FreeBSD version '.$min_bsd_version.' - '.$max_bsd_version.$nl);
echo('Your version of FreeBSD is supported!'.$nl);
// check system architecture too
$arch = trim(getcommand('sysctl -n hw.machine_arch'));
echo('** Detected architecture '.$arch.$nl);
if ($calculator == 'auto')
{
if ($arch == 'i386')
$calculator = 'calc';
elseif ($arch == 'amd64')
$calculator = 'internal';
else
error('Incompatible architecture: '.$arch.$nl);
echo('Using calculator '.$calculator.' based on automatic selection.'.$nl);
}
else
echo('Using explicitly defined calculator: '.$calculator.$nl);
/**
*** SANITY CHECKS
**/
if (!is_writable($tmp_dir))
error('Temporary directory "'.$tmp_dir.'" is not writable!');
if (disk_free_space($tmp_dir) < $min_free_space)
error('Temporary directory "'.$tmp_dir.'" has fewer than '.$min_free_space.' bytes of free disk space!');
// now check for all necessary external executables
echo($nl.'** Checking for required external executables'.$nl);
$missing_executables = array();
foreach ($required_executables as $exec)
{
if (!is_executable($exec))
$missing_executables[] = $exec;
}
if (count($missing_executables) > 0)
error('Your are missing executables: '.$nl.implode($nl,$missing_executables));
foreach ($required_executables as $exec)
echo('Found: '.$exec.$nl);
// now check for all necessary external files (non-executable)
echo($nl.'** Checking for required external files'.$nl);
$missing_files = array();
foreach ($required_files as $rfile)
{
if (!is_readable($rfile))
$missing_files[] = $rfile;
}
if (count($missing_files) > 0)
error('Your are missing files: '.$nl.implode($nl,$missing_files));
foreach ($required_files as $rfile)
echo('Found: '.$rfile.$nl);
echo($nl);
// now load the geom_nop kernel module
$cmd = getcommand('kldstat');
if (strpos($cmd,'geom_nop.ko') === false)
{
// geom_nop.ko unloaded; load the module
$cmd = getcommand('gnop load');
// check again if module is loaded
$cmd = getcommand('kldstat');
if (strpos($cmd,'geom_nop.ko') === false)
error('unable to load geom_nop.ko kernel module');
}
echo('** GNOP kernel module is activated'.$nl);
// make sure no GNOP devices exist yet
$cmd = getcommand('gnop status');
if ((strlen($cmd) > 0) OR (!is_string($cmd)))
error('it seems there are GNOP devices active, please make sure all GNOP devices are deactivated before expanding');
echo('** GNOP device list is clean'.$nl.$nl.$nl);
// make sure we use an up-to-date version of graid5
// because if we're using an older version, we will have total dataloss!
if (!file_exists($path_graid5_module))
error('Module "'.$path_graid5_module.'" not found!');
if (!file_exists($path_graid5_lib))
error('Library "'.$path_graid5_lib.'" not found!');
$mtime_module = filemtime($path_graid5_module);
$mtime_lib = filemtime($path_graid5_lib);
if ($mininum_date > $mtime_module)
error('Module "'.$path_graid5_module.'" is too old! Please update graid5 to >= 18-feb-2007');
if ($mininum_date > $mtime_lib)
error('Library "'.$path_graid5_lib.'" is too old! Please update graid5 to >= 18-feb-2007');
echo('** WARNING!'.$nl);
echo('It is absolutely imperative you are using an up-to-date version of geom_raid5. Please check the data below:'.$nl.$nl);
echo('Minimum date: '.date('d F Y',$minimum_date).$nl);
echo('Kernel module date: '.date('d F Y',$mtime_module).$nl);
echo('Library date: '.date('d F Y',$mtime_lib).$nl.$nl);
echo('Are you confident you are using the up-to-date version? (Y/N) ');
$line = trim(fgets(STDIN));
if (strtoupper($line) != 'Y')
die('Aborting'.$nl.$nl);
echo($nl.$nl);
// check if graid5 module is active
$cmd = getcommand('kldstat');
if (strpos($cmd,'geom_raid5.ko') === false)
error('graid5 module seems to be unloaded; please load it first');
// check if no rebuild is in progress
$cmd = getcommand('graid5 status '.$graid5_volume);
if (strpos($cmd,'REBUILDING') !== false)
error('there is a rebuild in progress; please finish the rebuild before running this script!');
// check if no FAILED/CRITICAL raid5 devices
$cmd = getcommand('graid5 status '.$graid5_volume);
if ((strpos($cmd,'FAILED') !== false) OR (strpos($cmd,'DEGRADED') !== false) OR (strpos($cmd,'CRITICAL') !== false))
error('some graid5 devices are in state FAILED, DEGRADED or CRITICAL. Expansion denied!');
// check if volume name exists
if (strpos($cmd,'raid5/'.$graid5_volume) === false)
error('there is no graid5 volume called "'.$graid5_volume.'"!');
// fetch the current members of the graid5 volume
$disk_members = array();
$lines = explode(chr(10),$cmd);
$i = false;
foreach ($lines as $line)
{
// skip first line
if ($i == false)
{
$i = true;
continue;
}
// get last part of line
$str = substr(strrchr($line,' '),1);
if (!file_exists('/dev/'.$str))
error('Member disk "'.$str.'" does not appear to exist');
if (strlen($str) > 0)
$disk_members[] = $str;
}
// check if user does not try to add a disk which is already part of the volume
foreach ($disks_to_be_added as $disk)
if (array_search($disk,$disk_members) !== false)
error('disk "'.$disk.'" is already a member of the volume you wish to expand!');
// check if volume is mounted
$cmd = getcommand('mount');
if (strpos($cmd,'/dev/raid5/'.$graid5_volume) !== false)
error('graid5 volume "'.$graid5_volume.'" is still mounted, please unmount it first!');
// fetch stripesize
$cmd = getcommand('graid5 list '.$graid5_volume);
$str = substr($cmd,strpos($cmd,$nl.'Stripesize: ')+strlen($nl.'Stripesize: '));
$stripesize = substr($str,0,strpos($str,chr(10)));
if ((int)$stripesize == 0)
error('Could not extract stripesize out of "graid5 list '.$graid5_volume.'" output');
// discover smallest consumer (disk member)
$smallest_disk_sectorcount = -1;
foreach ($disk_members as $disk)
{
// fetch disk size in sectors
$size_in_sectors = trim(`diskinfo $disk | cut -f4`);
// store is lowest size or first
if ((is_int($smallest_disk_sectorcount)) OR (!calc_sameorhigher($size_in_sectors,$smallest_disk_sectorcount)))
$smallest_disk_sectorcount = $size_in_sectors;
}
// calculate provider sectorcount and slack and report
$calculatedrawsectorcount = calc((count($disk_members)-1)." * $smallest_disk_sectorcount");
$calculatedusablesectorcount = calc($calculatedrawsectorcount." - ".(count($disk_members)));
$fullstripeblocksize = calc((count($disk_members)-1)." * $stripesize");
$fullstripeblockcount = calc_floor("$calculatedusablesectorcount / ($fullstripeblocksize / $default_sector_size)");
$calculatedprovidersectorcount = calc("($fullstripeblocksize * $fullstripeblockcount) / $default_sector_size");
$actualprovidersectorcount = trim(`diskinfo /dev/raid5/$graid5_volume | cut -f4`);
$minimalslacksectorcount = count($disk_members);
$calculatedslacksectorcount = calc("$calculatedusablesectorcount % ($fullstripeblocksize / $default_sector_size)");
$actualslacksectorcount = calc("$calculatedusablesectorcount - $actualprovidersectorcount");
echo('** Smallest disk size : '.$smallest_disk_sectorcount.' sectors ('.calc_round("$smallest_disk_sectorcount / (2 * 1024 * 1024)",1).' GB)'.$nl);
echo('** Stripe size : '.$stripesize.' bytes ('.calc("($stripesize / 1024)").' KB)'.$nl);
echo('** Full stripe block : '.$fullstripeblocksize.' bytes ('.calc("$fullstripeblocksize / 1024").' KB)'.$nl);
echo('** Full stripe block count : '.$fullstripeblockcount.' blocks'.$nl);
echo('** Calculated raw size : '.$calculatedrawsectorcount.' sectors'.$nl);
echo('** Calculated usable size : '.$calculatedusablesectorcount.' sectors'.$nl);
echo('** Calculated provider size : '.$calculatedprovidersectorcount.' sectors ('.calc_round("$calculatedprovidersectorcount / (2 * 1024 * 1024)",1).' GB)'.$nl);
echo('** Actual provider size : '.$actualprovidersectorcount.' sectors ('.calc_round("$actualprovidersectorcount / (2 * 1024 * 1024)",1).' GB)'.$nl);
echo('** Minimum slack size : '.($minimalslacksectorcount * 512).' bytes'.$nl);
echo('** Actual slack size : '.($actualslacksectorcount * 512).' bytes ('.calc_round("$actualslacksectorcount / 2",1).' KB)'.$nl);
// sanity checks: compare calculated provider sectorcount with actual count
if ($calculatedprovidersectorcount != $actualprovidersectorcount)
error('SANITY FAILURE! Calculated sector count deviates from actual provider size!'.$nl
.'Please report this to the author at info@fluffles.net!');
// sanity checks: make sure we have the slack space needed for safe expansion
if (!calc_sameorhigher($calculatedslacksectorcount, $minimalslacksectorcount))
error('EXPANSION IMPOSSIBLE: too little slack at end of provider "'.$graid5_volume.'", '
.'you need at least '.calc("$minimalslacksectorcount * 512").' bytes. Sorry.');
echo('** Result : OK'.$nl.$nl);
// sanity check if disks to be added are at least as big as the smallest disk
echo ('** Minimum disk size : '.$smallest_disk_sectorcount.' sectors ('.calc_round("$smallest_disk_sectorcount / (2 * 1024 * 1024)",1).' GB)'.$nl);
foreach ($disks_to_be_added as $disk)
{
// fetch disk size in sectors
$size_in_sectors = trim(`diskinfo $disk | cut -f4`);
// report
echo('** New disk '.$disk.' : '.$size_in_sectors.' sectors ('.calc_round("$size_in_sectors / (2 * 1024 * 1024)",1).' GB)'.$nl);
if (!calc_sameorhigher($size_in_sectors, $smallest_disk_sectorcount))
error('Disk '.$disk.' is smaller than '.$smallest_disk_sectorcount.' sectors ('.calc_round("$smallest_disk_sectorcount / (2 * 1024 * 1024)",1).' GB)'
.'. Expansion denied!');
}
echo('** Result : OK'.$nl.$nl);
// ask for new volume name
echo($nl.'Please enter a name for the new volume. Do not choose a volume name already in use.'.$nl.$nl);
echo('Name of new volume: ');
$line = trim(fgets(STDIN));
$new_volume = $line;
$status = getcommand('graid5 status');
if (!(strlen($new_volume) > 0))
error('Invalid volume name entered (too short)');
elseif (strpos($status,$new_volume) !== FALSE)
error('The chosen volume name appears to exist already!');
// present information
echo($nl.'Please check the information you provided below:'.$nl.$nl);
echo('graid5 volume: '.$graid5_volume.$nl);
echo('new volume name: '.$new_volume.$nl);
echo('Stripesize: '.$stripesize.$nl);
echo('Current disks: '.implode(' ',$disk_members).$nl);
echo('Disks to be added: '.implode(' ',$disks_to_be_added).$nl.$nl);
echo('Is the above information correct? (Y/N) ');
$line = trim(fgets(STDIN));
if (strtoupper($line) != 'Y')
die('Aborting'.$nl.$nl);
echo($nl.$nl);
/** Starting Expansion Procedure **/
echo('** Starting Expansion Procedure'.$nl);
echo('At this point we are ready to begin the procedure. Please make sure that:'.$nl.$nl);
echo('1. you have closed down each non-critical application (remember to keep sshd running if you login remotely)'.$nl);
echo('2. you do not turn off or reboot the system while expansion is running'.$nl);
echo('3. all disks are in good health and you have checked all disks for surface defects'.$nl.$nl);
echo('Do you wish to continue? (Y/N) ');
$line = trim(fgets(STDIN));
if (strtoupper($line) != 'Y')
die('Aborting'.$nl.$nl);
echo($nl.$nl);
// enable foot shooting permission
echo('** now setting geom debugflags'.$nl);
$command = 'sysctl kern.geom.debugflags=16';
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command "'.$command.'"');
echo($nl.$nl);
usleep(500000);
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// zero write second last metasector of each existing disk
foreach ($disk_members as $disk)
{
// fetch sectorsize
$sectorsize = trim(`diskinfo $disk | cut -f2`);
// fetch disk sector count
$sectorcount = trim(`diskinfo $disk | cut -f4`);
// do complex mathematics
$secondlastsector = calc("$sectorcount - 2");
// write zeroes to second last sector
echo('** writing zeros to sector '.$secondlastsector.' of disk '.$disk.$nl);
$command = 'dd if=/dev/zero of=/dev/'.$disk.' bs='.$sectorsize.' oseek='.$secondlastsector.' count=1 > /dev/null 2>&1';
if ($debug OR $verbose)
echo('executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command "'.$command.'"');
usleep(200000);
}
// zero write two last sectors on each new disk
foreach ($disks_to_be_added as $disk)
{
// fetch sectorsize
$sectorsize = trim(`diskinfo $disk | cut -f2`);
// fetch disk sector count
$sectorcount = trim(`diskinfo $disk | cut -f4`);
// do complex mathematics
$secondlastsector = calc("$sectorcount - 2");
// write zeroes to second last TWO sectors
echo('** writing zeros to TWO sectors starting at #'.$secondlastsector.' of disk '.$disk.$nl);
$command = 'dd if=/dev/zero of=/dev/'.$disk.' bs='.$sectorsize.' oseek='.$secondlastsector.' count=2 > /dev/null 2>&1';
if ($debug OR $verbose)
echo('executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command "'.$command.'"');
usleep(200000);
}
// create gnop containers for each disk
echo('** now creating GNOP devices'.$nl);
foreach ($disk_members as $disk)
{
// fetch sectorsize
$sectorsize = trim(`diskinfo $disk | cut -f2`);
// fetch disk size
$disksize = trim(`diskinfo $disk | cut -f3`);
// fetch disk sector count
$sectorcount = trim(`diskinfo $disk | cut -f4`);
// backup metadata
$command = 'dd if=/dev/'.$disk.' of=/graid5-metabackup-'.$disk.' bs='.$sectorsize.' iseek='.calc("$sectorcount - 1").' count=1 > /dev/null 2>&1';
if ($debug OR $verbose)
echo('executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command "'.$command.'"');
usleep(200000);
// create GNOP device
$command = 'gnop create -s '.calc("$disksize - $sectorsize").' '.$disk;
if ($debug OR $verbose)
echo('executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command "'.$command.'"');
// check if gnop is created as we would expect
if (!debug)
{
$sectorsize_nop = trim(`diskinfo ${disk}.nop | cut -f2`);
if ($sectorsize_nop != $sectorsize)
error('sector size of GNOP device ('.$disk.'.nop) and graid5 volume ('.$disk.') is different');
$disksize_nop = trim(`diskinfo ${disk}.nop | cut -f3`);
if ($disksize_nop != calc("$disksize - $sectorsize"))
error('disk size of GNOP device ('.$disk.'.nop) and graid5 volume ('.$disk.') is different');
}
// created succesfully
echo('created '.$disk.'.nop'.$nl);
}
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// activate new graid5 volume
echo($nl.'** now activating new graid5 volume ("expanded")'.$nl);
$nop_members = '';
foreach ($disk_members as $disk)
$nop_members .= $disk.'.nop ';
$command = 'graid5 label -h -n -s '.$stripesize.' '.$new_volume.' '.$nop_members.' '.implode(' ',$disks_to_be_added);
if ($debug OR $verbose)
echo('executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command "'.$command.'"');
usleep(500000);
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// now start copying data from old graid5 device to new graid5 device
// this implements the actual 'remapping' of data
echo($nl.'** Starting data remapping procedure'.$nl);
echo('WARNING: from this point the procedure is irreversable'.$nl.$nl);
echo('Do you wish to continue? (Y/N) ');
$line = trim(fgets(STDIN));
if (strtoupper($line) != 'Y')
die('Aborting'.$nl.$nl);
echo($nl.$nl);
// initialize remapping
$graid5_volume_size = trim(`diskinfo raid5/${graid5_volume} | cut -f3`);
if (calc_sameorhigher(0,$graid5_volume_size))
error('Could not properly detect graid5 volume size');
$i = 0;
$loopprotection = 10000000; // max 1PB
$starttime = time();
// remapping starts
echo('** Starting data transfer now...'.$nl);
while (true)
{
// execute remapping command
$command = 'dd if=/dev/raid5/'.$graid5_volume.' of=/dev/raid5/'.$new_volume.' bs='.$datamapping_chunksize.' '
.'iseek='.$i.' oseek='.$i.' count=1 conv=sync,noerror 2>&1';
if ($debug OR $verbose)
echo('executing: '.$command.$nl);
unset($output);
if (!$debug)
$txt = exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command "'.$command.'" -- OOPS! Contact an expert if you wish to keep your data!');
foreach ($output as $line)
if ($line == '0+0 records out')
{
// end reached
echo($nl.'Data transfer completed. Transferred between '.((($i-1)*$datamapping_chunksize)/(1024*1024)).' MB and '.(($i*$datamapping_chunksize)/(1024*1024)).' MB'.$nl);
// go to next step
break 2;
}
// display progress
$progress = calc_floor("(($i * $datamapping_chunksize) / $graid5_volume_size)*100",1);
if (calc_sameorhigher($progress,"100"))
{
if ($debug)
{
echo($nl.'Since we are running in debugmode, we cannot detect the transfer completion directly, '
.'so we will stop now by timer (100% progress)'.$nl);
break;
}
// overtime reached, this should not happen!
echo($nl);
var_dump($output);
echo($nl);
echo('Return value: '.$rv.$nl.$nl);
echo('WARNING: progress is exceeding 100%! This should not have happened. '
.'You may choose to continue with the next step or try to restore things manually. '
.'You are adviced to contact an expert if your data is important.'.$nl.$nl);
echo('Do you wish to continue? (Y/N) ');
$line = trim(fgets(STDIN));
if (strtoupper($line) != 'Y')
die('Aborting'.$nl.$nl);
else
echo($nl.'Data transfer completed. Transferred between '.calc("(($i-1)*$datamapping_chunksize)/(1024*1024)").' MB and '.calc("($i*$datamapping_chunksize)/(1024*1024)").' MB'.$nl);
break;
}
$currtime = time();
$deltatime = $currtime - $starttime;
if (($deltatime <= 0) OR (calc_sameorhigher("0",$progress)))
{
$eta = '?';
$progress = 0;
$speed = '?';
}
else
{
// speed is the total throughput (read + write)
$speed = calc_round("($i*$datamapping_chunksize) / (($deltatime*1024*1024) / 2)",1);
//oldcalc: $speed = round((real)($i*$datamapping_chunksize) / (real)(($deltatime*1024*1024) / 2),1);
$projectedtime = $deltatime / ((real)$progress / 100);
$remainingtime = (int)$projectedtime - $deltatime;
//newcalc: $projectedtime = calc("$deltatime / ($progress / 100)");
//newcalc: $remainingtime = calc("$projectedtime - $deltatime;
if ($remainingtime <= 60)
$eta = 'soon';
else
{
$hours = $remainingtime / 3600;
$minutes = ($remainingtime % 3600) / 60;
$eta = (int)$hours.' hours '.str_pad((int)$minutes,2,'0',STR_PAD_LEFT).' minutes';
}
}
if (strpos((string)$progress,'.') === false)
$progress .= '.0';
if ((int)($deltatime / 60) != $deltatime_old)
echo('=> data remapping in progress at '.$progress.'% (ETA: '.$eta.') @ '.$speed.' MB/s'.$nl);
$deltatime_old = (int)($deltatime / 60);
if ($i >= $loopprotection)
error('loop protection while remapping data -- OOPS! Contact an expert if you wish to keep your data!');
// next loop; increase $i
$i++;
}
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// remove old graid5 volume disks
echo($nl.$nl.'** stopping old graid5 volume'.$nl);
foreach ($disk_members as $disk)
{
$command = 'graid5 remove '.$graid5_volume.' '.$disk;
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume -- OOPS! Contact an expert if you wish to keep your data!');
echo('Removed disk '.$disk.' from volume "raid5/'.$graid5_volume.'"'.$nl);
}
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// destroy the old graid5 volume
$command = 'graid5 destroy '.$graid5_volume;
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume -- OOPS! Contact an expert if you wish to keep your data!');
echo('Destroyed old volume "raid5/'.$graid5_volume.'"'.$nl);
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// remove hardcoded property
echo($nl.'** Removing hardcoded property from new volume "raid5/'.$new_volume.'"'.$nl);
$command = 'graid5 configure -h '.$new_volume;
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume -- OOPS! Contact an expert if you wish to keep your data!');
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// sleep to let the change take effect
sleep(4);
// backup meta sectors of new volume
echo($nl.'** Backing up metasectors of new volume "raid5/'.$new_volume.'" disk members (excluding new members)'.$nl.$nl);
if ($debug)
echo('Since we are running in debugmode, please ignore any "diskinfo" errors as well as wrong dd parameters (bs and iseek) here.'.$nl);
foreach ($disk_members as $disk)
{
$disk = $disk.'.nop';
// fetch sectorsize
$sectorsize = trim(`diskinfo $disk | cut -f2`);
// fetch disk size
$disksize = trim(`diskinfo $disk | cut -f3`);
// fetch disk sector count
$sectorcount = trim(`diskinfo $disk | cut -f4`);
// backup metadata
$command = 'dd if=/dev/'.$disk.' of='.$tmp_dir.'graid5-newmetabackup-'.$disk.' '
.'bs='.$sectorsize.' count=1 iseek='.calc("$sectorcount - 1").' > /dev/null 2>&1';
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume -- OOPS! Contact an expert if you wish to keep your data!');
echo('Metasector backup of '.$disk.' complete'.$nl);
}
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// removing GNOP disks in new volume
echo($nl.'** Removing GNOP disks from new volume "raid5/'.$new_volume.'" (does not include new members)'.$nl.$nl);
foreach ($disk_members as $disk)
{
$disk = $disk.'.nop';
// remove disk
$command = 'graid5 remove '.$new_volume.' '.$disk;
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume)'.$nl
.'****** OOPS! Contact an expert if you wish to keep your data!');
echo('Removed '.$disk.' from new volume "raid5/'.$new_volume.'"'.$nl);
// now destroy gnop disk as well
$command = 'gnop destroy '.$disk;
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (destroying gnop device)'.$nl
.'****** OOPS! Contact an expert if you wish to keep your data!');
echo('Destroyed GNOP device '.$disk.$nl);
}
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// restore metabackups
echo($nl.'** Restoring metasector backups (does not include new members)'.$nl.$nl);
foreach ($disk_members as $disk)
{
// fetch sectorsize
$sectorsize = trim(`diskinfo $disk | cut -f2`);
// fetch disk size
$disksize = trim(`diskinfo $disk | cut -f3`);
// fetch disk sector count
$sectorcount = trim(`diskinfo $disk | cut -f4`);
$command = 'dd if='.$tmp_dir.'graid5-newmetabackup-'.$disk.'.nop of=/dev/'.$disk.' '
.'bs='.$sectorsize.' count=1 oseek='.calc("$sectorcount - 1").' > /dev/null 2>&1';
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume -- OOPS! Contact an expert if you wish to keep your data!');
echo('Restored metabackup of disk '.$disk.$nl);
}
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// unset NO HOT option for new volume
echo($nl.'** Removing "NO HOT" flag from new volume "'.$new_volume.'"'.$nl.$nl);
$command = 'graid5 configure -n '.$new_volume;
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume -- OOPS! Contact an expert if you wish to keep your data!');
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// sleep to let change take effect
sleep(4);
// zero write
echo('*** Starting zero-write at offset '.$actualprovidersectorsize.' sectors'.$nl);
echo('*** this could take a long time!'.$nl.$nl);
$newprovidersectorcount = trim(`diskinfo /dev/raid5/$new_volume | cut -f4`);
$sectorsgained = calc("$newprovidersectorcount - $actualprovidersectorcount");
// sanity
if (calc_sameorhigher(0,$sectorsgained))
error('Sectors gained ('.$sectorsgained.') is zero of negative. This should not have happened!');
// execute zero writing!
$command = 'dd if=/dev/zero of=/dev/raid5/'.$new_volume.' bs='.$fullstripeblocksize.' '
.'oseek='.$fullstripeblockcount.' count='.$sectorsgained.' conv=sync,noerror 2>&1';
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv > 1)
{
var_dump($output);
echo($nl.'Return value: '.$rv.$nl.$nl);
echo('** Got return value '.$rv.' on last command (zero write)'.$nl.$nl);
}
echo('*** Done with zero-write!'.$nl.$nl);
if ($stepbystep)
{
echo($nl.'---step-by-step marker---'.$nl);
$line = trim(fgets(STDIN));
}
// display device status
echo($nl.'Displaying current geom_raid5 volumes:'.$nl.$nl);
echo getcommand('graid5 status');
echo($nl.'You should now see a volume "'.$new_volume.'" with disk members '.implode(' ',$disk_members).' '.implode(' ',$disks_to_be_added).$nl);
echo('You should not see the old volume named "'.$graid5_volume.'" anymore.'.$nl);
echo('If you concur with the information above we can start the rebuild of the volume. '
.'If you see a problem, starting a rebuild may cause you to loose all data at this point.'.$nl);
echo('Do you wish to start the rebuild? (Y/N) ');
$line = trim(fgets(STDIN));
echo($nl);
if (strtoupper($line) != 'Y')
die('Aborting'.$nl.$nl);
// start rebuild
echo($nl.'** Starting rebuild of volume "'.$new_volume.'"'.$nl.$nl);
$command = 'graid5 configure -R '.$new_volume;
if ($debug OR $verbose)
echo('Executing: '.$command.$nl);
if (!$debug)
exec($command,$output,$rv);
if ($rv != 0)
error('Got return value '.$rv.' on last command (removing disk from old graid5 volume -- OOPS! Contact an expert if you wish to keep your data!');
sleep(1);
echo('Activated rebuild, check status below: '.$nl.$nl);
echo getcommand('graid5 status');
// the end
echo($nl.$nl);
echo('**'.$nl);
echo('*** geom_raid5 expansion has finished!'.$nl);
echo('*** =================================='.$nl);
echo('*** old size: '.calc("$actualprovidersectorcount / (1024*1024*1024 / $default_sector_size)").' GB'.$nl);
echo('*** new size: '.calc("$newprovidersectorcount / (1024*1024*1024 / $default_sector_size)").' GB'.$nl);
echo('*** size gained: '.calc("$sectorsgained / (1024*1024*1024 / $default_sector_size)").' GB'.$nl);
echo('**'.$nl.$nl);
echo('Expansion has finished. You should now do the following:'.$nl);
echo('1. check that your files are okay (fsck -t ufs /dev/raid5/'.$new_volume.')'.$nl);
echo('2. use growfs to enlarge the filesystem that still has the original size (growfs /dev/raid5/'.$new_volume.')'.$nl);
echo('3. check the filesystem again for errors (fsck -t ufs /dev/raid5/'.$new_volume.')'.$nl.$nl);
echo('Please remember that until the rebuild has finished, your array should be considered DEGRADED'.$nl
.'and does not gain the protection RAID5 offers. The author of this expansion script would very much'.$nl
.'like to hear feedback on your expansion experience. If anything went wrong, or if you have questions,'.$nl
.'or if you are just happy everything went as planned and are now enjoying a bigger graid5 volume, '.$nl
.'please contact:'.$nl.$nl);
echo('Author: Veronica ("Enlightenment")'.$nl);
echo('Email: info@fluffles.net'.$nl);
echo('Website:
http://www.fluffles.net/ (lots of info about FreeNAS and geom_raid5 here)'.$nl);
echo('Have a nice day.
'.$nl.$nl);
// tune in to
http://fluffles.net for geom reviews, benchmarks, articles and our support forum!
// the end
/*** functions ***/
function error($string)
// exits the script because of an error
{
global $nl;
echo($nl);
echo('ERROR: '.$string.$nl.$nl);
die();
}
function usage()
// explains the usage of this script to the user
{
global $nl, $argv;
echo($nl);
echo('Usage: '.$argv[0].' <graid5-volume> <disks to be added>'.$nl);
echo('Example: '.$argv[0].' data ad6 ad8'.$nl.$nl);
die();
}
function getcommand($command)
// executes an external command and returns the output as a string
{
exec($command,$output);
$concat = '';
foreach ($output as $line)
$concat .= $line.chr(10);
return $concat;
}
function calc($calculation, $scale = false)
// do calculations (64-bit INT safe)
{
global $calculator, $math_scale_range, $debug_calc, $nl;
if (($scale < 0) OR (!is_int($scale)))
$scale = (int)$math_scale_range;
if ($calculator == 'bc')
{
$command = 'echo "scale = '.(int)$scale.'; '.$calculation.'" | bc';
exec($command,$output,$rv);
if ($debug_calc)
echo('Calculating: '.$command.' [result = '.$output[0].']'.$nl);
if ($rv != 0)
error('Calculation ('.$calculation.') gave return value '.$rv.' using external BC binary -- sorry!');
return (string)$output[0];
}
elseif ($calculator == 'calc')
{
$command = 'calc -p "'.$calculation.'"';
exec($command,$output,$rv);
if (strpos($output[0],'.') !== false)
$result = substr($output[0],0,strpos($output[0],'.') + 1 + $scale);
else
$result = $output[0];
$result = str_replace('~','',$result);
if ($debug_calc)
echo('Calculating: '.$command.' [result = '.$result.']'.$nl);
if ($rv != 0)
error('Calculation ('.$calculation.') gave return value '.$rv.' using external CALC binary -- sorry!');
return (string)$result;
}
elseif ($calculator == 'internal')
{
$calcfunction = create_function('','return round(('.$calculation.'),'.$scale.');');
$result = $calcfunction();
if ($debug_calc)
echo('Calculating: '.$calculation.' - result = '.$result.$nl);
return (string)$result;
}
}
function calc_sameorhigher($var1, $var2)
// returns boolean TRUE if $var1 is equal or higher than $var2
{
global $debug_calc,$nl;
$result = calc("$var1 - $var2");
if ($result{0} == '-')
{
if ($debug_calc)
echo('Calculation comparison: ('.$result{0}.') '.$var1.' is NOT equal or higher than '.$var2.$nl);
return false;
}
else
{
if ($debug_calc)
echo('Calculation comparison: ('.$result{0}.') '.$var1.' is equal or higher than '.$var2.$nl);
return true;
}
}
function calc_floor($calculation, $scale = 0)
// returns an integer with any fraction truncated. i.e. 4.7 returns 4
// scale sometimes misbehaves so not very dependable
{
global $debug_calc,$nl;
$result = calc($calculation, 10);
if (strpos($result,'.') !== false)
$floor = substr($result,0,strpos($result,'.'));
else
$floor = $result;
if ($debug_calc)
echo('Floor calculation : '.$floor.$nl);
return $floor;
}
function calc_round($calculation, $scale)
// returns a rounded integer, i.e. 4.7 returns 5. scale sets the precision, a scale of 2 means 4.317 will become 4.32
// scale sometimes misbehaves so not very dependable
{
global $debug_calc,$nl;
$result = calc($calculation, 0);
if (strpos($result,'.') !== false)
{
if ($scale > 0)
$rounded = substr($result,0,strpos($result,'.') + 1 + (int)$scale);
else
$rounded = substr($result,0,strpos($result,'.'));
}
else
$rounded = $result;
if ($debug_calc)
echo('Rounded calculation : '.$rounded.$nl);
return $rounded;
}
?>
[/more]