power_apc.pm 7.64 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
3

#
4
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
24
25
#

26
27
28
#
# snmpit module for APC MasterSwitch power controllers
#
29
# supports new(ip), power(on|off|cyc[le],port), status
30
31
32
33
34
35
36
#

package snmpit_apc;

$| = 1; # Turn off line buffering on output

use SNMP;
37
38
use strict;

39
40
41
42
# XXX for configurations in which APC unit always returns error
# even when it works.
my $ignore_errors = 0;

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
sub new($$;$) {

    # The next two lines are some voodoo taken from perltoot(1)
    my $proto = shift;
    my $class = ref($proto) || $proto;

    my $devicename = shift;
    my $debug = shift;

    if (!defined($debug)) {
	$debug = 0;
    }

    if ($debug) {
	print "snmpit_apm module initializing... debug level $debug\n";
    }
59

60
    $SNMP::debugging = ($debug - 5) if $debug > 5;
61
62
    my $mibpath = "/usr/local/share/snmp/mibs";
    &SNMP::addMibDirs($mibpath);
63
64
65
66
    &SNMP::addMibFiles("$mibpath/SNMPv2-SMI.txt",
		       "$mibpath/SNMPv2-MIB.txt",
		       "$mibpath/RFC1155-SMI.txt",
		       "$mibpath/PowerNet-MIB.txt");
67
68
69
70
    $SNMP::save_descriptions = 1; # must be set prior to mib initialization
    SNMP::initMib();              # parses default list of Mib modules
    $SNMP::use_enums = 1;         #use enum values instead of only ints
    print "Opening SNMP session to $devicename..." if $debug;
71
    my $sess =new SNMP::Session(DestHost => $devicename, Community => 'private', Version => '1');
72
73
74
75
76
77
78
79
80
81
82
83
84
    if (!defined($sess)) {
	warn("ERROR: Unable to connect to $devicename via SNMP\n");
	return undef;
    }

    my $self = {};

    $self->{SESS} = $sess;
    $self->{DEBUG} = $debug;
    $self->{DEVICENAME} = $devicename;

    bless($self,$class);
    return $self;
85
86
}

87
my %CtlOIDS = (
88
89
90
91
    default => ["sPDUOutletCtl",
		"outletOn", "outletOff", "outletReboot"],
    rPDU    => ["rPDUOutletControlOutletCommand",
		"immediateOn", "immediateOff", "immediateReboot"]
92
93
);

94
sub power {
95
96
    my $self = shift;
    my $op = shift;
97
    my @ports = @_;
98
99
100
    my $oids = $CtlOIDS{"default"};
    my $type = SNMP::translateObj($self->{SESS}->get("sysObjectID.0"));

101
102
103
104
105
106
107
108
109
110
111
112
    print "Got type '$type'\n" if $self->{DEBUG};
    if (defined($type)) {
	if ($type eq "masterSwitchrPDU") {
	    $oids = $CtlOIDS{"rPDU"};
	}
	# XXX the AP8941 power controllers we have need to use this OID
	# else they return an error on set operations (though the operations
	# do work!)
	elsif ($type eq "masterSwitch.6") {
	    $oids = $CtlOIDS{"rPDU"};
	}
    }
113

114
115
116
117
#   "sPDUOutletCtl" is ".1.3.6.1.4.1.318.1.1.4.4.2.1.3";
    if    ($op eq "on")  { $op = @$oids[1]; }
    elsif ($op eq "off") { $op = @$oids[2]; }
    elsif ($op =~ /cyc/) { $op = @$oids[3]; }
118
119
120
121
122

    my $errors = 0;

    foreach my $port (@ports) {
	print STDERR "**** Controlling port $port\n" if ($self->{DEBUG} > 1);
123
	if ($self->UpdateField(@$oids[0],$port,$op)) {
124
125
126
	    print STDERR "Outlet #$port control failed.\n";
	    $errors++;
	}
127
    }
128
129

    return $errors;
130
131
}

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
sub status {
    my $self = shift;
    my $statusp = shift;
    my %status;

    my $StatOID = ".1.3.6.1.4.1.318.1.1.4.2.2";
    my $Status = 0;

    $Status = $self->{SESS}->get([[$StatOID,0]]);
    if (!defined $Status) {
	print STDERR $self->{DEVICENAME}, ": no answer from device\n";
	return 1;
    }
    print("Status is '$Status'\n") if $self->{DEBUG};

    if ($statusp) {
	my @stats = split '\s+', $Status;
	my $o = 1;
	foreach my $ostat (@stats) {
	    my $outlet = "outlet$o";
	    $status{$outlet} = $ostat;
	    $o++;
	}
	%$statusp = %status;
    }
157
158
159
160
161
162
163
164
165
166
167

    #
    # We can retrieve the total amperage in use (in tenths of amps) 
    # on an APC by retrieving the rPDULoadStatusLoad.  There are 
    # entries for each of the phases of power that the device supports,
    # and for each of the banks of power it provides.
    #
    # We could add either the phases or the banks, but since the phases
    # come first, we use them.  We grab the number of phases supported,
    # then use that as a limit on how many status load values we retrieve.
    #
168
169
170
    # The OID to retrieve the phases is: ".1.3.6.1.4.1.318.1.1.12.1.9"
    # for more recent units, or:         ".1.3.6.1.4.1.318.1.1.12.2.1.2"
    # for older ones;
171
    # the load status table OID is:      ".1.3.6.1.4.1.318.1.1.12.2.3.1.1.2".
172
    #
173
    my ($phases,$banks);
174

175
    $phases = $self->{SESS}->get([["rPDUIdentDeviceNumPhases",0]]);
176
    if (!$phases) {
177
178
179
180
181
182
183
184
	# not all models support this MIB, try another
	$phases = $self->{SESS}->get([["rPDULoadDevNumPhases",0]]);
	if (!$phases) {
	    # some don't support either, bail.
	    print STDERR "Query phase: IdentDeviceNumPhases/LoadDevNumPhases failed\n"
		if $self->{DEBUG};
	    return 0;
	}
185
186
    }

187
188
189
190
191
192
193
194
    $banks = $self->{SESS}->get([["rPDULoadDevNumBanks",0]]);
    if (!$banks) {
	# not clear if we really need this, so just continue
	print STDERR "Query phase: LoadDevNumBanks failed\n"
	    if $self->{DEBUG};
	$banks = 0;
    }

195
    print "Okay.\nPhase report was '$phases'\n" if $self->{DEBUG};
196
    print "Bank report was '$banks'\n" if $self->{DEBUG};
197
    my ($varname, $index, $power, $val, $done);
198
199
    my %perphase = ();
    my %perbank = ();
200
201
202
203
    my $oid = ["rPDULoadStatusLoad",1];

    $self->{SESS}->get($oid);
    while ($$oid[0] =~ /rPDULoad/) {
204
	print "rPDULoadStatusLoad returns: ", join('/', @$oid), "\n" if $self->{DEBUG} > 1;
205
206
207
208
209
        ($varname, $index, $val) = @{$oid};
        if ($varname eq "rPDULoadStatusLoad") {
            if ($index <= $phases) {
    		print "Raw current value $val\n" if $self->{DEBUG};
                $status{current} += $val;
210
211
212
213
		$perphase{$index} = $val;
            } elsif ($phases == 1 && $banks > 0) {
		$perbank{$index-$phases} = $val;
	    }
214
215
216
217
        }
        $self->{SESS}->getnext($oid);
    }

218
219
220
221
222
223
224
225
226
227
228
229
230
    if ($self->{DEBUG}) {
	print "Total raw current is $status{current}\n";
	print "Phases:\n";
	foreach my $i (sort keys %perphase) {
	    print "$i: ", $perphase{$i} / 10, "\n";
	}
	if ($banks > 0) {
	    print "Banks:\n";
	    foreach my $i (sort keys %perbank) {
		print "$i: ", $perbank{$i} / 10, "\n";
	    }
	}
    }
231
232
233
234
235
236
    $status{current} /= 10;

    if ($statusp) {
       %$statusp = %status;
    }

237
238
239
    return 0;
}

240
sub UpdateField {
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    my ($self,$OID,$port,$val) = @_;

    print "sess=$self->{SESS} $OID $port $val\n" if $self->{DEBUG} > 1;

    my $Status = 0;
    my $retval;

    print "Checking port $port of $self->{DEVICENAME} for $val..." if $self->{DEBUG};
    $Status = $self->{SESS}->get([[$OID,$port]]);
    if (!defined $Status) {
	print STDERR "Port $port, change to $val: No answer from device\n";
	return 1;
    } else {
	print "Okay.\nPort $port was $Status\n" if $self->{DEBUG};
	if ($Status ne $val) {
	    print "Setting $port to $val..." if $self->{DEBUG};
	    $retval = $self->{SESS}->set([[$OID,$port,$val,"INTEGER"]]);
258
259
	    $retval = "" if (!defined($retval));
	    print "Set returned '$retval'\n" if $self->{DEBUG};
260
261
262
263
264
265
266
267
268
	    if ($retval) {
		return 0;
	    }
	    # XXX warn, but otherwise ignore errors
	    if ($ignore_errors) {
		print STDERR "WARNING: $port '$val' failed, ignoring\n";
		return 0;
	    }
	    return 1;
269
270
	}
	return 0;
271
272
273
274
275
    }
}

# End with true
1;