Commit 1d87bf24 authored by Kirk Webb's avatar Kirk Webb

Add power support for moonshot.

This enhances the power_ipmi module in three ways:

* Will now check for auth creds in the outets_remoteauth table.
  - Previously the module had credentials hard-wired.
  - Key role in table should be "ipmi-passwd".

* Makes it run in parallel for the set of outlets provided
  - via emutil::ParRun().

* HP Moonshot chassis iLO support.
  - Device (node) type == "ipmi-ms".
  - Outlet to ipmi address resolution.
  - Additional required ipmitool parameters ("lanplus" protocol).

* Supports KGKEY for session encryption.
  - KGKEYs can be placed in the DB ("ipmi-kgkey" role, key encoded in hex).

Note that the "status" command doesn't really work presently, but that's
OK since it wasn't ever hooked in.
parent d82ed018
......@@ -390,8 +390,8 @@ foreach my $power_id (keys %outlets) {
# of object
#
my $errors = 0;
if ($type eq "IPMI") {
my $device = new power_ipmi($IP,$verbose);
if ($type eq "IPMI" || $type eq "ipmi-ms") {
my $device = new power_ipmi($type,$power_id,$verbose);
if (!defined $device) {
warn "Unable to contact controller for $nodestr. Skipping...\n";
next;
......
......@@ -36,14 +36,29 @@ package power_ipmi;
$| = 1; # Turn off line buffering on output
use strict;
use lib "@prefix@/lib";
use libdb;
use emutil;
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin'; # Required when using system() or backticks `` in combination with the perl -T taint checks
sub new($$;$) {
my $IPMITOOL = "/usr/local/bin/ipmitool";
my $KTYPE = "ipmi";
# Constants for moonshot chassis support.
my $MS_MAXSLOT = 45;
my $MS_NODEBASEADDR = 0x72;
my $MS_NODEBRIDGEID = 0x7;
my $MS_CARTBASEADDR = 0x82;
my $MS_CARTBRIDGEID = 0x0;
sub new($$$;$) {
# The next two lines are some voodoo taken from perltoot(1)
my $proto = shift;
my $class = ref($proto) || $proto;
my $devicetype = shift;
my $devicename = shift;
my $debug = shift;
......@@ -59,51 +74,93 @@ sub new($$;$) {
$self->{DEBUG} = $debug;
$self->{DEVICETYPE} = $devicetype;
$self->{DEVICENAME} = $devicename;
$self->{un} = "ADMIN"; # default username
$self->{pw} = "ADMIN"; # default password
$self->{USERNAME} = "ADMIN"; # default username
$self->{PASSWORD} = "ADMIN"; # default password
$self->{KGKEY} = "";
# Fetch authentication credentials from the DB.
my $res = DBQueryFatal("select key_role,key_uid,mykey" .
" from outlets_remoteauth" .
" where node_id='$devicename'".
" and key_type='$KTYPE'");
if (!defined($res) || !$res || $res->num_rows() == 0) {
warn "No remote auth info for $devicename. Using defaults!\n";
} else {
while (my $row = $res->fetchrow_hashref()) {
my $role = $row->{'key_role'};
if ($role eq "ipmi-passwd") {
$self->{USERNAME} = $row->{'key_uid'};
$self->{PASSWORD} = $row->{'mykey'};
}
elsif ($role eq "ipmi-kgkey") {
$self->{KGKEY} = $row->{'mykey'} =~ s/^0x//;
}
}
}
$self->{IPMICMD} = "$IPMITOOL -H $self->{DEVICENAME} -U $self->{USERNAME} -P $self->{PASSWORD}";
if ($self->{KGKEY}) {
$self->{IPMICMD} .= " -I lanplus -y $self->{KGKEY}";
} elsif ($self->{DEVICETYPE} eq "ipmi-ms") {
$self->{IPMICMD} .= " -I lanplus";
}
# Do a quick query, to see if it works
system("ipmitool -H $self->{DEVICENAME} -U $self->{un} -P $self->{pw} power status >/dev/null 2>\&1");
system("$self->{IPMICMD} power status >/dev/null 2>\&1");
if ( $? != 0 ) { # system() sets $? to a non-zero value in case of failure
warn "ERROR: Unable to connect to $devicename via IPMI\n";
return undef;
}
else {
bless($self,$class);
return $self;
}
bless($self,$class);
$self->_mkmsmap();
return $self;
}
sub power {
sub power($$@) {
my $self = shift;
my $op = shift;
my @ports = @_; # Doesn't really matter, an IPMI card only has a single "outlet", which is for the node the card is installed in
my @outlets = @_;
my $errors = 0;
my ($retval, $output);
if ($op eq "on") { $op = "power on"; }
elsif ($op eq "off") { $op = "power off"; }
elsif ($op =~ /cyc/) { $op = "power reset"; }
elsif ($op =~ /cyc/) {
if ($self->{DEVICETYPE} eq "ipmi-ms") {
$op = "power cycle";
} else {
$op = "power reset";
}
}
($retval,$output) = $self->_execipmicmd($op);
if ( $retval != 0 ) { # increment $errors if return value was not 0
$errors++;
print STDERR $self->{DEVICENAME}, ": could not control power status of device\n";
($errors, $output) = $self->_execipmicmd($op, @outlets);
if ($errors) {
print STDERR $self->{DEVICENAME}, ": command '$op' failed for some/all outlets: @outlets\n";
}
return $errors;
}
sub status {
# XXX: This isn't actually hooked in, and won't work for the moonshot
# as it is.
sub status($$$) {
my $self = shift;
my $type = shift;
my $statusp = shift; # pointer to an associative (hashed) array (i.o.w. passed by reference)
my %status; # local associative array which we'll pass back through $statusp
my $errors = 0;
my ($retval, $output);
# XXX: No support for the moonshot right now.
if ($type eq "ipmi-ms") {
return 0;
}
# Get power status (i.e. whether system is on/off)
($retval,$output) = $self->_execipmicmd("power status");
......@@ -127,22 +184,92 @@ sub status {
return $errors;
}
sub _execipmicmd { # _ indicates that this is a private method (Perl convention)
my ($self,$op) = @_;
my $ipmicmd = "ipmitool -H $self->{DEVICENAME} -U $self->{un} -P $self->{pw} "; # ipmitool command syntax, to be completed
# with an operation to perform
my $cmd = $ipmicmd . $op;
my $output;
if ( $self->{DEBUG} > 1 ) {
print STDERR "**** Executing $cmd\n";
$output = `$cmd`; # get output of the $cmd, e.g. many lines of sensor readings or "Chassis power is on"
chomp ( $output ); # remove the \n at the end, if any
sub _execipmicmd($$@) { # _ indicates that this is a private method (Perl convention)
my ($self, $op, @outlets) = @_;
my $exitval = 0;
my $output = "";
my @results = ();
my $coderef = sub {
my $outlet = shift;
my $output = "";
my $cmd;
# Construct command based on device type.
if ($self->{DEVICETYPE} eq "ipmi-ms") {
my $ipmiaddrstr = $self->_get_msaddr($outlet);
if ($ipmiaddrstr) {
$cmd = "$self->{IPMICMD} $ipmiaddrstr $op";
} else {
print STDERR "$self->{DEVICENAME}: invalid outlet: $outlet\n";
return -1;
}
} else {
$cmd = "$self->{IPMICMD} $op";
}
print STDERR "**** Executing $cmd\n"
if ($self->{DEBUG});
$output = `$cmd`; # get output of the $cmd, e.g. many lines of sensor readings or "Chassis power is on"
chomp ( $output ); # remove the \n at the end, if any
print STDERR "impitool output:\n$output\n"
if ($self->{DEBUG});
if ($?) {
print STDERR "*** power_ipmi: ipmitool returned non-zero: $?\n";
print STDERR "ipmitool output:\n$output\n";
return -1;
}
return 0;
};
if (ParRun(undef, \@results, $coderef, @outlets)) {
print STDERR "*** power_ipmi: Internal error in ParRun()!\n";
return (-1,undef);
}
#
# Check the exit codes.
#
foreach my $result (@results) {
++$exitval
if ($result != 0);
}
return ($exitval, $output);
}
sub _mkmsmap() {
my $self = shift;
$self->{MS_OUTLETMAP} = [];
foreach my $outlet (1 .. $MS_MAXSLOT) {
my $cartaddr = $MS_CARTBASEADDR + 2*($outlet - 1);
my $nodeaddr = $MS_NODEBASEADDR;
my $vals = {
'cartaddr' => $cartaddr,
'cartbrid' => $MS_CARTBRIDGEID,
'nodeaddr' => $nodeaddr,
'nodebrid' => $MS_NODEBRIDGEID,
};
$self->{MS_OUTLETMAP}->[$outlet] = $vals;
}
else { $output = `$cmd 2>/dev/null`; } # if there's any stderr output, silence it
# (system() and backticks `` execute the command using the /bin/sh shell)
}
sub _get_msaddr($$) {
my ($self, $outlet) = @_;
my $msaddrstr = "";
return undef
unless defined($self->{MS_OUTLETMAP}->[$outlet]);
my $msaddr = $self->{MS_OUTLETMAP}->[$outlet];
$msaddrstr = sprintf("-T 0x%x -B 0x%x -t 0x%x -b 0x%x",
$msaddr->{'cartaddr'},
$msaddr->{'cartbrid'},
$msaddr->{'nodeaddr'},
$msaddr->{'nodebrid'});
return ($?, $output);
return $msaddrstr;
}
# End with true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment