diff options
Diffstat (limited to 'perl-install/raid.pm')
-rw-r--r-- | perl-install/raid.pm | 406 |
1 files changed, 316 insertions, 90 deletions
diff --git a/perl-install/raid.pm b/perl-install/raid.pm index 0d0a8720a..f20a511eb 100644 --- a/perl-install/raid.pm +++ b/perl-install/raid.pm @@ -1,4 +1,4 @@ -package raid; # $Id$ +package raid; use diagnostics; use strict; @@ -7,68 +7,101 @@ use strict; #- misc imports #-###################################################################################### use common; -use partition_table qw(:types); +use fs::type; +use fs::get; use run_program; use devices; use modules; -use fs; -sub nb { - my ($nb) = @_; - first((ref $nb ? $nb->{device} : $nb) =~ /(\d+)/); +=head1 SYNOPSYS + +Manage regular soft RAID (MD=Multiple Drive). + +=head1 Functions + +=over + +=cut + +sub max_nb() { 131 } + +sub check_prog { + # perl_checker: require interactive + my ($in) = @_; # perl_checker: $in = interactive->new + $::prefix ? whereis_binary('mdadm') : $in->do_pkgs->ensure_binary_is_installed('mdadm', 'mdadm'); } sub new { - my ($raids, @parts) = @_; - my $nb = @$raids; - $raids->[$nb] = { 'chunk-size' => "64k", type => 0x83, disks => [ @parts ], device => "md$nb", notFormatted => 1, level => 1 }; - foreach my $part (@parts) { - $part->{raid} = $nb; - delete $part->{mntpoint}; + my ($raids, %opts) = @_; + my $md_part = { %opts }; + add2hash_($md_part, { 'chunk-size' => '64', disks => [], + fs_type => defaultFS(), + device => first(free_mds($raids)), + notFormatted => 1, level => 1 }); + push @$raids, $md_part; + foreach (@{$md_part->{disks}}) { + $_->{raid} = $md_part->{device}; + fs::type::set_pt_type($_, 0xfd); + delete $_->{mntpoint}; } - update($raids->[$nb]); - $nb; + update($md_part); + $md_part; } +=item add() + +Add a partition to a RAID array + +=cut + sub add { - my ($raids, $part, $nb) = @_; $nb = nb($nb); - $raids->[$nb]{isMounted} and die _("Can't add a partition to _formatted_ RAID md%d", $nb); - $part->{raid} = $nb; + my ($md_part, $part) = @_; + $md_part->{isMounted} and die N("Cannot add a partition to _formatted_ RAID %s", $md_part->{device}); + inactivate_and_dirty($md_part); + set_isFormatted($part, 0); + $part->{raid} = $md_part->{device}; delete $part->{mntpoint}; - push @{$raids->[$nb]{disks}}, $part; - update($raids->[$nb]); + push @{$md_part->{disks}}, $part; + update($md_part); } -sub delete { - my ($raids, $nb) = @_; - $nb = nb($nb); +=item delete() - delete $_->{raid} foreach @{$raids->[$nb]{disks}}; - undef $raids->[$nb]; +Remove a partition from a RAID array + +=cut + +sub delete { + my ($raids, $md_part) = @_; + inactivate_and_dirty($md_part); + delete $_->{raid} foreach @{$md_part->{disks}}; + @$raids = grep { $_ != $md_part } @$raids; + write_conf($raids) if $::isStandalone; } -sub changeNb { - my ($raids, $oldnb, $newnb) = @_; - if ($oldnb != $newnb) { - ($raids->[$newnb], $raids->[$oldnb]) = ($raids->[$oldnb], undef); - $raids->[$newnb]{device} = "md$newnb"; - $_->{raid} = $newnb foreach @{$raids->[$newnb]{disks}}; +sub change_device { + my ($md_part, $new_device) = @_; + if ($new_device ne $md_part->{device}) { + inactivate_and_dirty($md_part); + $md_part->{device} = $new_device; + $_->{raid} = $new_device foreach @{$md_part->{disks}}; } - $newnb; } sub removeDisk { my ($raids, $part) = @_; - my $nb = nb($part->{raid}); - run_program::run("raidstop", devices::make($part->{device})); + my $md_part = fs::get::device2part($part->{raid}, $raids); + inactivate_and_dirty($md_part); + fs::type::set_isFormatted($part, 0); delete $part->{raid}; - my $disks = $raids->[$nb]{disks}; + my $disks = $md_part->{disks}; @$disks = grep { $_ != $part } @$disks; if (@$disks) { - update($raids->[$nb]); + update($md_part); } else { - undef $raids->[$nb]; + @$raids = grep { $_ != $md_part } @$raids; } + write_conf($raids) if $::isStandalone; } sub updateSize { @@ -77,63 +110,79 @@ sub updateSize { my @l = map { $_->{size} } @{$part->{disks}}; $part->{size} = do { - if (/0|linear/) { sum @l } - elsif (/1/ ) { min @l } - elsif (/4|5/) { min(@l) * $#l } + if (/0|linear/) { sum @l } + elsif (/1/) { min @l } + elsif (/4|5/) { min(@l) * (@l - 1) } + elsif (/6/) { min(@l) * (@l - 2) } + elsif (/10/) { min(@l) * (@l / 2) } }; } -sub module { - my ($part) = @_; - my $mod = $part->{level}; +=item allmodules() + +Return list of the RAID modules we support + +=cut - $mod = 5 if $mod eq "4"; - $mod = "raid$mod" if $mod =~ /^\d+$/; - $mod; +sub allmodules { + ('raid0', 'raid1', 'raid10', 'raid456'); } -sub updateIsFormatted { +=item module($part) + +Return list of modules need by a md device (according to its RAID level) + +=cut + +sub module { my ($part) = @_; - $part->{isFormatted} = and_ map { $_->{isFormatted} } @{$part->{disks}}; - $part->{notFormatted} = and_ map { $_->{notFormatted} } @{$part->{disks}}; -} -sub update { - foreach (@_) { - updateSize($_); - updateIsFormatted($_); + my $level = $part->{level}; + log::l("level $level"); + + if (member($level, 4, 5, 6)) { + 'raid456'; + } elsif (member($level, 10)) { + 'raid10'; + } elsif ($level =~ /^\d+$/) { + "raid$level"; + } else { + $level; } } -sub write { - my ($raids, $file) = @_; - local *F; - local $\ = "\n"; - open F, ">$file" or die _("Can't write file %s", $file); - - foreach (grep {$_} @$raids) { - print F <<"EOF"; -raiddev /dev/$_->{device} -raid-level $_->{level} -chunk-size $_->{'chunk-size'} -persistent-superblock 1 -EOF - print F "nr-raid-disks ", int @{$_->{disks}}; - map_index { - print F " device ", devices::make($_->{device}); - print F " raid-disk $::i"; - } @{$_->{disks}}; - } + +sub update { + updateSize($_) foreach @_; } sub make { - my ($raids, $part) = @_; + my ($raids, $part) = @_; + + return if is_active($part->{device}); + + inactivate_and_dirty($part); + isRAID($_) and make($raids, $_) foreach @{$part->{disks}}; - my $dev = devices::make($part->{device}); eval { modules::load(module($part)) }; - &write($raids, "/etc/raidtab"); - run_program::run("raidstop", $dev); - run_program::run("mkraid", "--really-force", $dev) or die - $::isStandalone ? _("mkraid failed (maybe raidtools are missing?)") : _("mkraid failed"); + + whereis_binary('mdadm') or die 'mdadm not installed'; + + my $dev = devices::make($part->{device}); + my $nb = @{$part->{disks}}; + + run_program::run_or_die('mdadm', '--create', '--run', $dev, + if_($nb == 1, '--force'), + if_($nb == 2 && $part->{level} == 10, '--layout=f2'), + if_($part->{level} != 1, '--chunk=' . $part->{'chunk-size'}), + "--level=$part->{level}", + "--raid-devices=$nb", + if_($part->{metadata}, "--metadata=$part->{metadata}"), + map { devices::make($_->{device}) } @{$part->{disks}}); + + if (my $raw_part = get_md_info($dev)) { + $part->{UUID} = $raw_part->{UUID}; + } + write_conf($raids) if $::isStandalone; } sub format_part { @@ -141,28 +190,205 @@ sub format_part { $part->{isFormatted} and return; make($raids, $part); - fs::real_format_part($part); - $_->{isFormatted} = 1 foreach @{$part->{disks}}; + fs::format::part_raw($part, undef); + set_isFormatted($_, 1) foreach @{$part->{disks}}; } sub verify { my ($raids) = @_; - $raids or return; - foreach (grep {$_} @$raids) { - @{$_->{disks}} >= ($_->{level} =~ /4|5/ ? 3 : 2) or die _("Not enough partitions for RAID level %d\n", $_->{level}); + foreach (@$raids) { + my $nb = $_->{level} =~ /4|5|6/ ? 3 : 2; + @{$_->{disks}} >= $nb or die N("Not enough partitions for RAID level %d\n", $_->{level}); } } -sub prepare_prefixed { - my ($raids, $prefix) = @_; - $raids or return; +sub inactivate_and_dirty { + my ($part) = @_; + run_program::run('mdadm', '--stop', devices::make($part->{device})); + set_isFormatted($part, 0); +} + +=item active_mds() + +Return list of active MDs + +=cut + +sub active_mds() { + map { if_(/^(md\S+)\s*:\s*active/, $1) } cat_("/proc/mdstat"); +} + +=item inactive_mds() + +Return list of inactive MDs + +=cut + +sub inactive_mds() { + map { if_(/^(md\S+)\s*:\s*inactive/, $1) } cat_("/proc/mdstat"); +} - eval { cp_af("/etc/raidtab", "$prefix/etc/raidtab") }; - foreach (grep {$_} @$raids) { - devices::make("$prefix/dev/$_->{device}") foreach @{$_->{disks}}; +=item free_mds() + +Return list of unused MD device nodes + +=cut + +sub free_mds { + my ($raids) = @_; + difference2([ map { "md$_" } 0 .. max_nb() ], [ map { $_->{device} } @$raids ]); +} + +=item detect_durting_install() + +Load RAID modules. +Stop RAIDS that might have been started too early by udev. +Scan & starts RAID arrays, then stop any inactive md. + +=cut + +sub detect_during_install { + my (@parts) = @_; + eval { modules::load($_) } foreach allmodules(); + + # udev may have started raids but failed due to not yet loaded modules and + # they remains inactive ("md: personality for level 1 is not loaded!") + stop_inactive_mds(); + + detect_during_install_once(@parts); + detect_during_install_once(@parts) if active_mds(); #- try again to detect RAID 10 + stop_inactive_mds(); +} + +=item stop_inactive_mds() + +Stop any inactive md. + +=cut + +sub stop_inactive_mds() { + foreach (inactive_mds()) { + log::l("$_ is an inactive md, we stop it to ensure it doesn't busy devices"); + run_program::run('mdadm', '--stop', devices::make($_)); + } +} + +=item detect_during_install_once(@parts) + +Scan & starts RAID arrays, then stop any inactive md. + +=cut + +sub detect_during_install_once { + my (@parts) = @_; + devices::make("md$_") foreach 0 .. max_nb(); + output('/etc/mdadm.conf', join(' ', 'DEVICE', + (map { "/dev/$_" } active_mds()), + map { devices::make($_->{device}) } @parts), "\n"); + run_program::run('mdadm', '>>', '/etc/mdadm.conf', '--examine', '--scan'); + run_program::run('mdadm', '--assemble', '--scan'); +} + +sub get_existing { + my @parts = @_; + my $raids = []; + foreach my $md (active_mds()) { + my $raw_part = get_md_info(devices::make($md)) or next; + + $raw_part->{level} =~ s/raid//; #- { linear | raid0 | raid1 | raid5 | raid6 | raid10 } -> { linear | 0 | 1 | 5 | 6 | 10 } + + my @mdparts = + map { + if (my $part = fs::get::device2part($_, [ @parts, @$raids ])) { + $part; + } else { + log::l("ERROR: unknown raw raid device $_"); + (); + } + } split(',', $raw_part->{devices}); + + my ($info) = $md =~ m!([^/]*)$!; + my $md_part = new($raids, + device => $md, + UUID => $raw_part->{UUID}, + level => $raw_part->{level}, + metadata => $raw_part->{metadata}, + info => $info . " (RAID$raw_part->{level})", + disks => \@mdparts); + + my $type = fs::type::type_subpart_from_magic($md_part); + if ($type) { + put_in_hash($md_part, $type); + } else { + fs::type::set_fs_type($md_part, defaultFS()); + } + my $fs_type = $type && $type->{fs_type}; + fs::type::set_isFormatted($md_part, to_bool($fs_type)); + + log::l("RAID: found $md (raid $md_part->{level}) type $fs_type with parts $raw_part->{devices}"); } + $raids; } -sub stopAll() { run_program::run("raidstop", devices::make("md$_")) foreach 0..7 } +=item is_active($dev) + +Is it an?active md + +=cut + +sub is_active { + my ($dev) = @_; + member($dev, active_mds()); +} + +=item write_conf() + +Write /etc/mdadm.conf + +=cut + +sub write_conf { + my ($raids) = @_; + + @$raids or return; + + my @devices = uniq(map { devices::make($_->{device}) } map { @{$_->{disks}} } @$raids); + + # $::isInstall test for draklive-install: + output($::isInstall ? "$::prefix/etc/mdadm.conf" : "/etc/mdadm.conf", + join(' ', 'DEVICE', @devices) . "\n", + map { "ARRAY " . devices::make($_->{device}) . " UUID=$_->{UUID} auto=yes\n" } @$raids); +} + +sub get_md_info { + my ($dev) = @_; + my $conf = parse_mdadm_conf(scalar run_program::get_stdout('mdadm', '--detail', '--brief', '-v', $dev)); + + @{$conf->{ARRAY}} or return; + @{$conf->{ARRAY}} == 1 or internal_error("too many answers"); + $conf->{ARRAY}[0]; +} + +sub parse_mdadm_conf { + my ($s) = @_; + my %conf = (DEVICE => [], ARRAY => []); + $s =~ s!^\s*#.*!!gm; #- remove comments + $s =~ s!\n(\s)!$1!g; #- join lines starting with a space + foreach (split("\n", $s)) { + if (/^DEVICE\s+(.*)/) { + push @{$conf{DEVICE}}, split(' ', $1); + } elsif (my ($md, $md_conf) = /^ARRAY\s+(\S+)\s*(.*)/) { + my %md_conf = map { if_(/(.*)=(.*)/, $1 => $2) } split(' ', $md_conf); + $md_conf{level} =~ s/^raid//; + $md_conf{device} = $md; + push @{$conf{ARRAY}}, \%md_conf; + } + } + \%conf; +} + +=back + +=cut 1; |