mirror of
https://github.com/xcat2/xcat-core.git
synced 2025-05-21 19:22:05 +00:00
520 lines
16 KiB
Perl
520 lines
16 KiB
Perl
# IBM(c) 2013 EPL license http://www.eclipse.org/legal/epl-v10.html
|
|
#----------------------------------------------------------------------
|
|
|
|
# Plugin to interface with IBM SVC managed storage
|
|
#
|
|
use strict;
|
|
|
|
package xCAT_plugin::svc;
|
|
|
|
use xCAT::SvrUtils qw/sendmsg/;
|
|
use xCAT::SSHInteract;
|
|
use Getopt::Long;
|
|
Getopt::Long::Configure("bundling");
|
|
Getopt::Long::Configure("pass_through");
|
|
|
|
my $callback;
|
|
my $dorequest;
|
|
my %controllersessions;
|
|
|
|
sub handled_commands {
|
|
return {
|
|
mkstorage => "storage:type",
|
|
lsstorage => "storage:type",
|
|
detachstorage => "storage:type",
|
|
rmstorage => "storage:type",
|
|
lspool => "storage:type",
|
|
}
|
|
}
|
|
|
|
sub detachstorage {
|
|
my $request = shift;
|
|
my @nodes = @{ $request->{node} };
|
|
my $controller;
|
|
@ARGV = @{ $request->{arg} };
|
|
unless (GetOptions(
|
|
'controller=s' => \$controller,
|
|
)) {
|
|
foreach (@nodes) {
|
|
sendmsg([ 1, "Error parsing arguments" ], $callback, $_);
|
|
}
|
|
}
|
|
my $storagetab = xCAT::Table->new('storage');
|
|
my $storents = $storagetab->getNodesAttribs(\@nodes, [qw/controller/]);
|
|
unless ($controller) {
|
|
$controller = assure_identical_table_values(\@nodes, $storents, 'controller');
|
|
}
|
|
my @volnames = @ARGV;
|
|
my $wwns = get_wwns(@nodes);
|
|
use Data::Dumper;
|
|
my %namemap = makehosts($wwns, controller => $controller, cfg => $storents);
|
|
foreach my $node (keys %namemap) {
|
|
my $host = $namemap{$node};
|
|
my $session = establish_session(controller => $controller);
|
|
foreach my $volname (@volnames) {
|
|
my @rets = $session->cmd("rmvdiskhostmap -host $host $volname");
|
|
my $ret = $rets[0];
|
|
if ($ret =~ m/^CMMVC5842E/) {
|
|
sendmsg([ 1, "Node not attached to $volname" ], $callback, $node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub rmstorage {
|
|
my $request = shift;
|
|
my @nodes = @{ $request->{node} };
|
|
my $controller;
|
|
@ARGV = @{ $request->{arg} };
|
|
unless (GetOptions(
|
|
'controller=s' => \$controller,
|
|
)) {
|
|
foreach (@nodes) {
|
|
sendmsg([ 1, "Error parsing arguments" ], $callback, $_);
|
|
}
|
|
}
|
|
my @volnames = @ARGV;
|
|
my $storagetab = xCAT::Table->new('storage');
|
|
my $storents = $storagetab->getNodesAttribs(\@nodes, [qw/controller/]);
|
|
unless ($controller) {
|
|
$controller = assure_identical_table_values(\@nodes, $storents, 'controller');
|
|
}
|
|
detachstorage($request);
|
|
my $session = establish_session(controller => $controller);
|
|
foreach my $volname (@volnames) {
|
|
my @info = $session->cmd("rmvdisk $volname");
|
|
my $ret = $info[0];
|
|
if ($ret =~ m/^CMMVC5753E/) {
|
|
foreach my $node (@nodes) {
|
|
sendmsg([ 1, "Disk $volname does not exist" ], $callback, @nodes);
|
|
}
|
|
} elsif ($ret =~ m/^CMMVC5840E/) {
|
|
foreach my $node (@nodes) {
|
|
sendmsg([ 1, "Disk $volname is mapped to other nodes and/or busy" ], $callback, @nodes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
sub lsstorage {
|
|
my $request = shift;
|
|
my @nodes = @{ $request->{node} };
|
|
my $storagetab = xCAT::Table->new("storage", -create => 0);
|
|
unless ($storagetab) { return; }
|
|
my $storents = $storagetab->getNodesAttribs(\@nodes, [qw/controller/]);
|
|
my $wwns = get_wwns(@nodes);
|
|
foreach my $node (@nodes) {
|
|
if ($storents and $storents->{$node} and $storents->{$node}->[0]->{controller}) {
|
|
my $ctls = $storents->{$node}->[0]->{controller};
|
|
foreach my $ctl (split /,/, $ctls) { # TODO: scan all controllers at once
|
|
my $session = establish_session(controller => $ctl);
|
|
my %namemap = makehosts($wwns, controller => $ctl, cfg => $storents);
|
|
my @vdisks = hashifyoutput($session->cmd("lsvdisk -delim :"));
|
|
foreach my $vdisk (@vdisks) {
|
|
my @maps = hashifyoutput($session->cmd("lsvdiskhostmap -delim : " . $vdisk->{'id'}));
|
|
foreach my $map (@maps) {
|
|
if ($map->{host_name} eq $namemap{$node}) {
|
|
sendmsg($vdisk->{name} . ': size: ' . $vdisk->{capacity} . ' id: ' . $vdisk->{vdisk_UID}, $callback, $node);
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub mkstorage {
|
|
my $request = shift;
|
|
my @nodes = @{ $request->{node} };
|
|
my $shared = 0;
|
|
my $controller;
|
|
my $pool;
|
|
my $size;
|
|
my $boot = 0;
|
|
my $format = 0;
|
|
unless (ref $request->{arg}) {
|
|
die "TODO: usage";
|
|
}
|
|
my $name;
|
|
@ARGV = @{ $request->{arg} };
|
|
unless (GetOptions(
|
|
'format' => \$format,
|
|
'shared' => \$shared,
|
|
'controller=s' => \$controller,
|
|
'boot' => \$boot,
|
|
'size=f' => \$size,
|
|
'name=s' => \$name,
|
|
'pool=s' => \$pool,
|
|
)) {
|
|
foreach (@nodes) {
|
|
sendmsg([ 1, "Error parsing arguments" ], $callback, $_);
|
|
}
|
|
}
|
|
if ($shared and $boot) {
|
|
foreach (@nodes) {
|
|
sendmsg([ 1, "Storage can not be both shared and boot" ], $callback, $_);
|
|
}
|
|
}
|
|
my $storagetab = xCAT::Table->new('storage');
|
|
my $storents = $storagetab->getNodesAttribs(\@nodes,
|
|
[qw/controller storagepool size/]);
|
|
if ($shared) {
|
|
unless ($size) {
|
|
foreach (@nodes) {
|
|
sendmsg([ 1,
|
|
"Size for shared volumes must be specified as an argument"
|
|
], $callback, $_);
|
|
}
|
|
}
|
|
unless ($pool) {
|
|
$pool = assure_identical_table_values(\@nodes, $storents, 'storagepool');
|
|
}
|
|
unless ($controller) {
|
|
$controller = assure_identical_table_values(\@nodes, $storents, 'controller');
|
|
}
|
|
unless (defined $pool and defined $controller) {
|
|
return;
|
|
}
|
|
my %lunargs = (controller => $controller, size => $size, pool => $pool);
|
|
if ($name) { $lunargs{name} = $name; }
|
|
my $lun = create_lun(%lunargs);
|
|
sendmsg($lun->{name} . ": id: " . $lun->{wwn}, $callback);
|
|
my $wwns = get_wwns(@nodes);
|
|
my %namemap = makehosts($wwns, controller => $controller, cfg => $storents);
|
|
my @names = values %namemap;
|
|
bindhosts(\@names, $lun, controller => $controller);
|
|
|
|
if ($format) {
|
|
my %request = (
|
|
node => [ $nodes[0] ],
|
|
command => ['formatdisk'],
|
|
arg => [ '--id', $lun->{wwn}, '--name', $lun->{name} ]
|
|
);
|
|
$dorequest->(\%request, $callback);
|
|
%request = (
|
|
node => \@nodes,
|
|
command => ['rescansan'],
|
|
);
|
|
$dorequest->(\%request, $callback);
|
|
}
|
|
} else {
|
|
foreach my $node (@nodes) {
|
|
mkstorage_single(node => $node, size => $size, pool => $pool,
|
|
boot => $boot, name => $name, controller => $controller,
|
|
cfg => $storents->{$node});
|
|
}
|
|
}
|
|
}
|
|
|
|
sub hashifyoutput {
|
|
my @svcoutput = @_;
|
|
my $hdr = shift @svcoutput;
|
|
my @columns = split /:/, $hdr;
|
|
my @ret;
|
|
foreach my $line (@svcoutput) {
|
|
my $index = 0;
|
|
my %record = ();
|
|
my $keyname;
|
|
foreach my $datum (split /:/, $line) {
|
|
$keyname = $columns[$index];
|
|
$record{$keyname} = $datum;
|
|
$index += 1;
|
|
}
|
|
push @ret, \%record;
|
|
}
|
|
pop @ret; # discard data from prompt
|
|
return @ret;
|
|
}
|
|
|
|
sub bindhosts {
|
|
my $nodes = shift;
|
|
my $lun = shift;
|
|
my %args = @_;
|
|
my $session = establish_session(%args);
|
|
foreach my $node (@$nodes) {
|
|
|
|
#TODO: get what failure looks like... somehow...
|
|
#I guess I could make something with mismatched name and see how it
|
|
#goes
|
|
$session->cmd("mkvdiskhostmap -force -host $node " . $lun->{id});
|
|
}
|
|
}
|
|
|
|
sub fixup_host {
|
|
my $session = shift;
|
|
my $wwnlist = shift;
|
|
my @hosts = hashifyoutput($session->cmd("lshost -delim :"));
|
|
my %wwnmap;
|
|
my %hostmap;
|
|
foreach my $host (@hosts) {
|
|
my @hostd = $session->cmd("lshost -delim : " . $host->{name});
|
|
foreach my $hdatum (@hostd) {
|
|
if ($hdatum =~ m/^WWPN:(.*)$/) {
|
|
$wwnmap{$1} = $host->{name};
|
|
$hostmap{ $host->{name} }->{$1} = 1;
|
|
}
|
|
}
|
|
}
|
|
my $name;
|
|
foreach my $wwn (@$wwnlist) {
|
|
$wwn =~ s/://g;
|
|
$wwn = uc($wwn);
|
|
if (defined $wwnmap{$wwn}) { # found the matching host
|
|
#we want to give the host all the ports that may be relevant
|
|
$name = $wwnmap{$wwn};
|
|
foreach my $mwwn (@$wwnlist) {
|
|
$mwwn =~ s/://g;
|
|
$mwwn = uc($mwwn);
|
|
if (not defined $hostmap{$name}->{$mwwn}) {
|
|
$session->cmd("addhostport -hbawwpn $mwwn -force $name");
|
|
}
|
|
}
|
|
return $name;
|
|
}
|
|
}
|
|
die "unable to find host to fixup";
|
|
}
|
|
|
|
sub makehosts {
|
|
my $wwnmap = shift;
|
|
my %args = @_;
|
|
my $session = establish_session(%args);
|
|
my $stortab = xCAT::Table->new('storage');
|
|
my %nodenamemap;
|
|
foreach my $node (keys %$wwnmap) {
|
|
my $wwnstr = "";
|
|
foreach my $wwn (@{ $wwnmap->{$node} }) {
|
|
$wwn =~ s/://g;
|
|
$wwnstr .= $wwn . ":";
|
|
}
|
|
chop($wwnstr);
|
|
|
|
#TODO: what if the given wwn exists, but *not* as the nodename we want
|
|
#the correct action is to look at hosts, see if one exists, and reuse,
|
|
#create, or warn depending
|
|
my @hostres = $session->cmd("mkhost -name $node -hbawwpn $wwnstr -force");
|
|
my $result = $hostres[0];
|
|
if ($result =~ m/^CMM/) { # we have some exceptional case....
|
|
if ($result =~ m/^CMMVC6035E/) { #duplicate name and/or wwn..
|
|
#need to finde the host and massage it to being viable
|
|
$nodenamemap{$node} = fixup_host($session, $wwnmap->{$node});
|
|
} else {
|
|
die $result . " while trying to create host";
|
|
}
|
|
} else {
|
|
$nodenamemap{$node} = $node;
|
|
}
|
|
my @currentcontrollers = split /,/, $args{cfg}->{$node}->[0]->{controller};
|
|
if ($args{cfg}->{$node}->[0] and $args{cfg}->{$node}->[0]->{controller}) {
|
|
@currentcontrollers = split /,/, $args{cfg}->{$node}->[0]->{controller};
|
|
} else {
|
|
@currentcontrollers = ();
|
|
}
|
|
if (grep { $_ eq $args{controller} } @currentcontrollers) {
|
|
next;
|
|
}
|
|
unshift @currentcontrollers, $args{controller};
|
|
my $ctrstring = join ",", @currentcontrollers;
|
|
$stortab->setNodeAttribs($node, { controller => $ctrstring });
|
|
}
|
|
return %nodenamemap;
|
|
}
|
|
|
|
my %wwnmap;
|
|
|
|
sub got_wwns {
|
|
my $rsp = shift;
|
|
foreach my $ndata (@{ $rsp->{node} }) {
|
|
my $nodename = $ndata->{name}->[0];
|
|
my @wwns = ();
|
|
foreach my $data (@{ $ndata->{data} }) {
|
|
push @{ $wwnmap{$nodename} }, $data->{contents}->[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
sub get_wwns {
|
|
%wwnmap = ();
|
|
my @nodes = @_;
|
|
foreach my $node (@nodes) {
|
|
$wwnmap{$node} = [];
|
|
}
|
|
my %request = (
|
|
node => \@nodes,
|
|
command => ['rinv'],
|
|
arg => ['wwn']
|
|
);
|
|
$dorequest->(\%request, \&got_wwns);
|
|
return \%wwnmap;
|
|
}
|
|
|
|
my $globaluser;
|
|
my $globalpass;
|
|
|
|
sub get_svc_creds {
|
|
my $controller = shift;
|
|
if ($globaluser and $globalpass) {
|
|
return { 'user' => $globaluser, 'pass' => $globalpass }
|
|
}
|
|
my $passtab = xCAT::Table->new('passwd', -create => 0);
|
|
my $passent = $passtab->getAttribs({ key => 'svc' }, qw/username password/);
|
|
$globaluser = $passent->{username};
|
|
$globalpass = $passent->{password};
|
|
return { 'user' => $globaluser, 'pass' => $globalpass }
|
|
}
|
|
|
|
sub establish_session {
|
|
my %args = @_;
|
|
my $controller = $args{controller};
|
|
if ($controllersessions{$controller}) {
|
|
return $controllersessions{$controller};
|
|
}
|
|
|
|
#need to establish a new session
|
|
my $cred = get_svc_creds($controller);
|
|
my $sess = new xCAT::SSHInteract(-username => $cred->{user},
|
|
-password => $cred->{pass},
|
|
-host => $controller,
|
|
-output_record_separator => "\r",
|
|
|
|
#Errmode=>"return",
|
|
#Input_Log=>"/tmp/svcdbgl",
|
|
Prompt => '/>$/');
|
|
unless ($sess and $sess->atprompt) { die "TODO: cleanly handle bad login" }
|
|
$controllersessions{$controller} = $sess;
|
|
return $sess;
|
|
}
|
|
|
|
sub create_lun {
|
|
my %args = @_;
|
|
my $session = establish_session(%args);
|
|
my $pool = $args{pool};
|
|
my $size = $args{size};
|
|
my $cmd = "mkvdisk -iogrp io_grp0 -mdiskgrp $pool -size $size -unit gb";
|
|
if ($args{name}) {
|
|
$cmd .= " -name " . $args{name};
|
|
}
|
|
my @result = $session->cmd($cmd);
|
|
if ($result[0] =~ m/Virtual Disk, id \[(\d*)\], successfully created/) {
|
|
my $diskid = $1;
|
|
my $name;
|
|
my $wwn;
|
|
@result = $session->cmd("lsvdisk $diskid");
|
|
foreach (@result) {
|
|
chomp;
|
|
if (/^name (.*)\z/) {
|
|
$name = $1;
|
|
} elsif (/^vdisk_UID (.*)\z/) {
|
|
$wwn = $1;
|
|
}
|
|
}
|
|
return { name => $name, id => $diskid, wwn => $wwn };
|
|
}
|
|
}
|
|
|
|
sub assure_identical_table_values {
|
|
my $nodes = shift;
|
|
my $storents = shift;
|
|
my $attribute = shift;
|
|
my $lastval;
|
|
foreach my $node (@$nodes) {
|
|
my $sent = $storents->{$node}->[0];
|
|
unless ($sent) {
|
|
sendmsg([ 1, "No $attribute in arguments or table" ],
|
|
$callback, $node);
|
|
return undef;
|
|
}
|
|
my $currval = $sent->{$attribute};
|
|
unless ($currval) {
|
|
sendmsg([ 1, "No $attribute in arguments or table" ],
|
|
$callback, $node);
|
|
return undef;
|
|
}
|
|
if ($lastval and $currval ne $lastval) {
|
|
sendmsg([ 1,
|
|
"$attribute mismatch in table config, try specifying as argument" ],
|
|
$callback, $node);
|
|
return undef;
|
|
}
|
|
if (not defined $lastval) { $lastval = $currval; }
|
|
}
|
|
return $lastval;
|
|
}
|
|
|
|
sub mkstorage_single {
|
|
my %args = @_;
|
|
my $size;
|
|
my $cfg = $args{cfg};
|
|
my $node = $args{node};
|
|
my $pool;
|
|
my $controller;
|
|
if (defined $args{size}) {
|
|
$size = $args{size};
|
|
} elsif ($cfg->{size}) {
|
|
$size = $cfg->{size};
|
|
} else {
|
|
sendmsg([ 1, "Size not provided via argument or storage.size" ],
|
|
$callback, $node);
|
|
}
|
|
if (defined $args{pool}) {
|
|
$pool = $args{pool};
|
|
} elsif ($cfg->{storagepool}) {
|
|
$pool = $cfg->{storagepool};
|
|
} else {
|
|
sendmsg([ 1, "Pool not provided via argument or storage.storagepool" ],
|
|
$callback, $node);
|
|
}
|
|
if (defined $args{controller}) {
|
|
$controller = $args{controller};
|
|
} elsif ($cfg->[0]->{controller}) {
|
|
$controller = $cfg->[0]->{controller};
|
|
$controller =~ s/.*,//;
|
|
}
|
|
my %lunargs = (controller => $controller, size => $size, pool => $pool);
|
|
if ($args{name}) {
|
|
$lunargs{name} = $args{name} . "-" . $node;
|
|
}
|
|
my $lun = create_lun(%lunargs);
|
|
sendmsg($lun->{name} . ": id: " . $lun->{wwn}, $callback, $node);
|
|
my $wwns = get_wwns($node);
|
|
my %namemap = makehosts($wwns, controller => $controller, cfg => { $node => $cfg });
|
|
my @names = values %namemap;
|
|
bindhosts(\@names, $lun, controller => $controller);
|
|
}
|
|
|
|
sub process_request {
|
|
my $request = shift;
|
|
$callback = shift;
|
|
$dorequest = shift;
|
|
if ($request->{command}->[0] eq 'mkstorage') {
|
|
mkstorage($request);
|
|
} elsif ($request->{command}->[0] eq 'lsstorage') {
|
|
lsstorage($request);
|
|
} elsif ($request->{command}->[0] eq 'rmstorage') {
|
|
rmstorage($request);
|
|
} elsif ($request->{command}->[0] eq 'detachstorage') {
|
|
detachstorage($request);
|
|
} elsif ($request->{command}->[0] eq 'lspool') {
|
|
lsmdiskgrp($request);
|
|
}
|
|
foreach (values %controllersessions) {
|
|
$_->close();
|
|
}
|
|
}
|
|
|
|
sub lsmdiskgrp {
|
|
my $req = shift;
|
|
foreach my $node (@{ $req->{node} }) {
|
|
my $session = establish_session(controller => $node);
|
|
my @pools = hashifyoutput($session->cmd("lsmdiskgrp -delim :"));
|
|
foreach my $pool (@pools) {
|
|
sendmsg($pool->{name} . " available capacity: " . $pool->{free_capacity}, $callback, $node);
|
|
sendmsg($pool->{name} . " total capacity: " . $pool->{capacity}, $callback, $node);
|
|
}
|
|
}
|
|
}
|
|
|
|
1;
|