xcat-core/xCAT-server/lib/xcat/plugins/svc.pm

497 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;
unless (ref $request->{arg}) {
die "TODO: usage";
}
my $name;
@ARGV = @{$request->{arg}};
unless (GetOptions(
'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);
} 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;