[Nut-upsdev] SNMP agent for NUT

Luiz Angelo Daros de Luca luizluca at gmail.com
Sat Jun 23 19:13:13 UTC 2012


Hello Arnaud,

Thanks for your feedback!

2012/6/11 Arnaud Quette <aquette.dev at gmail.com>

>
> 2012/6/10 Luiz Angelo Daros de Luca <luizluca at gmail.com>
>
>> Hello guys,
>>
>
> Hello Luiz
>
>
>> I was some years ago when I sent something to this list.
>>
>
> yup, Google told me it was in 2009, on the megatec driver...
>
>
>> NUT is a great tool but lacks SNMP management. It can access a device
>> using SNMP
>> but I found no way to read NUT values using SNMP. It would be very
>> useful in order to integrate with
>> network management software based on SNMP. As I did not find a
>> solution, I wrote it myself.
>>
>
> well, there is (was/will be) one, but never finished nor integrated:
> - a set of 3 tasks, among which 2 are agent related:
>
> https://alioth.debian.org/pm/task.php?group_project_id=31&group_id=30602&func=browse
> - a patch with an agent based on RFC1628 (so limited to 1 UPS, no multiple
> devices, no PDU nor whatever...)
>
> https://alioth.debian.org/tracker/index.php?func=detail&aid=312563&group_id=30602&atid=411544
>

I found this MIB but, as you already said, it was too much limited. So, I
just wrote a new one.


>
>
>
>> I wrote a MIB file and a SNMP agent for net-snmp (pass_persist) in
>> order to expose the contents of upsc command to SNMP softwares. I
>> works simply by adding to snmpd.conf:
>>
>> pass_persist .1.3.6.1.4.1.26376.99 /my/path/to/snmp.nut -f
>>
>> The script and MIB files are at:
>>
>> https://github.com/luizluca/nut-snmpagent
>>
>> It is, for sure, not finished. I did not find a IANA enterprise
>> number, that is no costs to register, for NUT so I
>> spoofed my company one. I'm still using a fake upsc output in order to
>> have a more complex mix of devices.
>> I collected some output examples in the documentation but I might
>> still missed some special case.
>>
>
> I can provide some .dev files for simulation using dummy-ups driver.
> this should help you in testing corner cases...
> note that there is already a .dev and a .seq in the source distribution,
> "data" sub directory.
>

Interesting! This is one think that I needed. I'll check the existing data
and, if I need something special,
I'll ask if you have it.



>
> Also, I did not extensively tested it with real hardware. Anyway, my
>> UPS is quite simple for that.
>>
>> I got two minor doc bugs in the process. The first is that the
>> driver.version.data, that I found in some examples, its not listed as
>> a variable.
>>
>
> right, there are still some data missing in nut-names.txt, but I'm ramping
> up to fix these lacks.
>
>
> driver.flag.*
> driver.parameter.*
> => scripts/augeas/nutupsconf.aug
> ==> ups_fields stores all drivers parameters and flags (no distinction
> here yet between VAR_VALUE and VAR_FLAG)
>
> The other is the description of "power.minimum" as "Maximum seen
>> apparent power (VA)".
>>
>
> the is a typo error, fixed in r3653. thanks for your report.
> Note that we may investigate future mechanisms to share data mapping and
> descriptions, that would ease maintenance work on the SNMP agent.
>

I generated the MIB mostly with scripts and using the nut doc as input.
However, there is some parts that just need to be hand made.
I don't think that it will be possible to automatize all the update
process. In my case, I used the MIB information converted to XML (using
smidump)
in order to build most of the snmp-agent code.


>
>
>> I would appreciate some feedback/help/suggestions in any part of the
>> solution. I'm willing to release it with any permissive license.
>> The agent might not fit the requirements of all users (it is written
>> in ruby with a lot of metaprogramming) but the MIB file
>> seems to be in a good shape, expect for the missing IANA number. BTW,
>> I'm no SNMP expert.
>>
>
> all this is great!
> the SNMP agent (and a NUT MIB, or generic power devices MIB) is one of the
> things I've not had time to take care for years.
>
> I'm also going to look at the IANA process to get an OID for NUT.
> this should probably be under "org" and not "enterprise", as for the
> generic "UPS MIB" (aka RFC-1628, located under mgmt.mib-2)
>
>
I guess "enterprise" is not only for commercial companies. Even net-snmp
uses it. It is a generic term like 3rd party.


> that said, I'm not a fan of Ruby.
>

No problem :-). But I must say it is pretty fast to develop.


> moreover, the general layout of the below MIB should be reworked a bit to
> match 1:1 NUT naming scheme (Ie the NUT data collections).
> more info below...
>
> Regards,
>>
>> PS: Just for an example, the current output that I get from a snmpwalk
>> is like this (for 6 devices):
>>
>> NUT-MIB::deviceIndex.1 = INTEGER: 1
>> NUT-MIB::deviceIndex.2 = INTEGER: 2
>> NUT-MIB::deviceIndex.3 = INTEGER: 3
>> NUT-MIB::deviceIndex.4 = INTEGER: 4
>> NUT-MIB::deviceIndex.5 = INTEGER: 5
>> NUT-MIB::deviceIndex.6 = INTEGER: 6
>> NUT-MIB::deviceName.1 = STRING: ups2
>> NUT-MIB::deviceName.2 = STRING: xxx
>> NUT-MIB::deviceName.3 = STRING: upsoutlet
>> NUT-MIB::deviceName.4 = STRING: ups3p1
>> NUT-MIB::deviceName.5 = STRING: ups3p2
>> NUT-MIB::deviceName.6 = STRING: ups3
>> NUT-MIB::deviceDesc.1 = STRING: UPS2 10 KVA Lacerda Titan Black tri-mono
>> 10KVA (220v) Serial A08823221
>> NUT-MIB::deviceDesc.2 = STRING: Fictious
>> NUT-MIB::deviceDesc.3 = STRING: Example outlet
>> NUT-MIB::deviceDesc.4 = STRING: phases1
>> NUT-MIB::deviceDesc.5 = STRING: phases2
>> NUT-MIB::deviceDesc.6 = STRING: test3
>> NUT-MIB::deviceModel.1 = STRING: Titan Black tri-mono 10KVA
>> NUT-MIB::deviceModel.2 = STRING: Titan Black tri-mono 10KVA
>> NUT-MIB::deviceModel.6 = STRING: Ellipse MAX 1100
>> NUT-MIB::deviceMfr.1 = STRING: Lacerda Sistemas de Energia
>> NUT-MIB::deviceMfr.2 = STRING: Lacerda Sistemas de Energia
>> NUT-MIB::deviceMfr.6 = STRING: EATON
>> NUT-MIB::deviceSerial.1 = STRING: A08823221
>> NUT-MIB::deviceSerial.2 = STRING: A08823221
>> NUT-MIB::deviceSerial.6 = STRING: ADKK22008
>> NUT-MIB::deviceType.1 = STRING: ups
>> NUT-MIB::deviceType.2 = STRING: ups
>> NUT-MIB::deviceType.6 = STRING: ups
>> NUT-MIB::upsStatus.1 = STRING: OL
>> NUT-MIB::upsStatus.2 = STRING: OL
>> NUT-MIB::upsStatus.6 = STRING: OL CHRG
>> NUT-MIB::upsModel.6 = STRING: Ellipse MAX 1100
>> NUT-MIB::upsMfr.6 = STRING: EATON
>> NUT-MIB::upsSerial.6 = STRING: ADKK22008
>> NUT-MIB::upsVendorid.6 = STRING: 0463
>> NUT-MIB::upsProductid.6 = STRING: ffff
>> (...)
>> <http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/nut-upsdev>
>
>
> I would more see something like this (including the collection name in the
> path):
> NUT-MIB::device.deviceIndex.1 = INTEGER: 1
> NUT-MIB::device.deviceName.1 = STRING: ups2
> NUT-MIB::device.deviceDesc.1 = STRING: UPS2 ...
> NUT-MIB::device.deviceModel.1 = STRING: Titan Black tri-mono 10KVA
> NUT-MIB::device.deviceMfr.1 = STRING: Lacerda Sistemas de Energia
> (...)
>
>
> NUT-MIB::inputCurrentNominal.1 = INTEGER: 27.00
> would give something like
> NUT-MIB::input.l1.inputCurrent.nominal
> (equivalent to NUT "input.L1.current.nominal:  27.0")
>
>
According to my little SNMP knowledge (I'm really no expert), I guess this
is not possible.
SNMP have two basic structures for data: scalars and tables. There is no
complex data structure as
a "C struct" or json syntax, like upsc uses as output.

Table have fixed columns, which hold the data, and (multiple) index(es).
There is no table in table or dynamic objects. The most complex
that you can have is a multiple index table that can reference the index of
a foreign table. "input.l1.inputCurrent.nominal" is just not possible.
The closest would be to create a table for each domain/subdomain/context
set like:

nutInputBypassL2-L3Table.nutInputBypassL2-L3Entry.nutCurrentNominal.x
(where x is the device index)

Scalars are used for values that are unique in the snmp service like
machine uptime, hostname and so on.
I used it for "nut server information". They use a fixed "index/instance" 0.

So, I created tables for device, ups, input, output, battery, all these
that have only one value for device. They look like these:

(The output in the first email wasn't showing the full path. In this email,
I listed the full path but removed the
".iso.org.dod.internet.private.enterprises.tresc.nutMIB.nutMIBObjects."
that preceded each line)

nutDeviceTable.nutDeviceEntry.nutDeviceIndex.1 = INTEGER: 1
nutDeviceTable.nutDeviceEntry.nutDeviceIndex.2 = INTEGER: 2
nutDeviceTable.nutDeviceEntry.nutDeviceIndex.3 = INTEGER: 3
nutDeviceTable.nutDeviceEntry.nutDeviceIndex.4 = INTEGER: 4
nutDeviceTable.nutDeviceEntry.nutDeviceIndex.5 = INTEGER: 5
nutDeviceTable.nutDeviceEntry.nutDeviceIndex.6 = INTEGER: 6
nutDeviceTable.nutDeviceEntry.nutDeviceName.1 = STRING: ups2
nutDeviceTable.nutDeviceEntry.nutDeviceName.2 = STRING: xxx
nutDeviceTable.nutDeviceEntry.nutDeviceName.3 = STRING: upsoutlet
nutDeviceTable.nutDeviceEntry.nutDeviceName.4 = STRING: ups3p1
nutDeviceTable.nutDeviceEntry.nutDeviceName.5 = STRING: ups3p2
nutDeviceTable.nutDeviceEntry.nutDeviceName.6 = STRING: ups3
nutDeviceTable.nutDeviceEntry.nutDeviceDesc.1 = STRING: UPS2 10 KVA Lacerda
Titan Black tri-mono 10KVA (220v) Serial A08823221
nutDeviceTable.nutDeviceEntry.nutDeviceDesc.2 = STRING: Fictious
nutDeviceTable.nutDeviceEntry.nutDeviceDesc.3 = STRING: Example outlet
nutDeviceTable.nutDeviceEntry.nutDeviceDesc.4 = STRING: phases1
nutDeviceTable.nutDeviceEntry.nutDeviceDesc.5 = STRING: phases2
nutDeviceTable.nutDeviceEntry.nutDeviceDesc.6 = STRING: test3
nutDeviceTable.nutDeviceEntry.nutDeviceModel.1 = STRING: Titan Black
tri-mono 10KVA
nutDeviceTable.nutDeviceEntry.nutDeviceModel.2 = STRING: Titan Black
tri-mono 10KVA
nutDeviceTable.nutDeviceEntry.nutDeviceModel.6 = STRING: Ellipse MAX 1100
(...)

and

nutOutputTable.nutOutputEntry.nutOutputVoltage.1 = INTEGER: 221.0
nutOutputTable.nutOutputEntry.nutOutputVoltage.2 = INTEGER: 221.0
nutOutputTable.nutOutputEntry.nutOutputVoltage.5 = INTEGER: 120.0
nutOutputTable.nutOutputEntry.nutOutputVoltage.6 = INTEGER: 230.0
nutOutputTable.nutOutputEntry.nutOutputVoltageNominal.6 = INTEGER: 230.0
nutOutputTable.nutOutputEntry.nutOutputFrequencyNominal.5 = INTEGER: 60.0
nutOutputTable.nutOutputEntry.nutOutputFrequencyNominal.6 = INTEGER: 50.0
nutOutputTable.nutOutputEntry.nutOutputCurrent.5 = INTEGER: 244.20
nutOutputTable.nutOutputEntry.nutOutputPhases.4 = INTEGER: 3
nutOutputTable.nutOutputEntry.nutOutputPhases.5 = INTEGER: 1

nut*Entry is mandatory for snmp structure.
Outlets have a relation of 1-to-N with devices. So, I created a table that
uses as index deviceIndex and outletIndex. The result is like this:

nutOutletTable.nutOutletEntry.nutOutletIndex.3.0 = INTEGER: 0
nutOutletTable.nutOutletEntry.nutOutletIndex.3.1 = INTEGER: 1
nutOutletTable.nutOutletEntry.nutOutletIndex.3.2 = INTEGER: 2
nutOutletTable.nutOutletEntry.nutOutletIndex.6.0 = INTEGER: 0
nutOutletTable.nutOutletEntry.nutOutletIndex.6.1 = INTEGER: 1
nutOutletTable.nutOutletEntry.nutOutletId.3.0 = STRING: 0
nutOutletTable.nutOutletEntry.nutOutletId.3.1 = STRING: 1
nutOutletTable.nutOutletEntry.nutOutletId.3.2 = STRING: 2
nutOutletTable.nutOutletEntry.nutOutletId.6.0 = STRING: 1
nutOutletTable.nutOutletEntry.nutOutletId.6.1 = STRING: 2
nutOutletTable.nutOutletEntry.nutOutletDesc.3.0 = STRING: Main Outlet
nutOutletTable.nutOutletEntry.nutOutletDesc.3.1 = STRING: PowerShare Outlet
1
nutOutletTable.nutOutletEntry.nutOutletDesc.3.2 = STRING: PowerShare Outlet
2
nutOutletTable.nutOutletEntry.nutOutletDesc.6.0 = STRING: Main Outlet
nutOutletTable.nutOutletEntry.nutOutletDesc.6.1 = STRING: PowerShare Outlet
1
nutOutletTable.nutOutletEntry.nutOutletSwitch.3.1 = INTEGER: on(1)
nutOutletTable.nutOutletEntry.nutOutletSwitch.3.2 = INTEGER: on(1)
nutOutletTable.nutOutletEntry.nutOutletStatus.6.1 = INTEGER: on(1)
nutOutletTable.nutOutletEntry.nutOutletSwitchable.3.0 = INTEGER: yes(1)
nutOutletTable.nutOutletEntry.nutOutletSwitchable.3.1 = INTEGER: yes(1)
nutOutletTable.nutOutletEntry.nutOutletSwitchable.3.2 = INTEGER: yes(1)
nutOutletTable.nutOutletEntry.nutOutletSwitchable.6.0 = INTEGER: no(0)
nutOutletTable.nutOutletEntry.nutOutletSwitchable.6.1 = INTEGER: no(0)
(...)

For 3 phase devices, The number of columns in the table would be very big.
So, I opted to
use 4-level index using: (deviceIndex, domain(input/ouptut),
subdomain(load, bypass,...) and context (none, L1, L2, L1-L2).
The result is like this:

nutThreephaseTable.nutThreephaseEntry.nutThreephaseDomain.4.output.load.l1
= INTEGER: output(2)
nutThreephaseTable.nutThreephaseEntry.nutThreephaseDomain.5.output.load.none
= INTEGER: output(2)
nutThreephaseTable.nutThreephaseEntry.nutThreephaseSubdomain.4.output.load.l1
= INTEGER: load(4)
nutThreephaseTable.nutThreephaseEntry.nutThreephaseSubdomain.5.output.load.none
= INTEGER: load(4)
nutThreephaseTable.nutThreephaseEntry.nutThreephaseContext.4.output.load.l1
= INTEGER: l1(2)
nutThreephaseTable.nutThreephaseEntry.nutThreephaseContext.5.output.load.none
= INTEGER: none(0)
nutThreephaseTable.nutThreephaseEntry.nutThreephaseCurrent.4.input.mains.l1
= INTEGER: 133.00
nutThreephaseTable.nutThreephaseEntry.nutThreephaseCurrent.5.output.load.none
= INTEGER: 244.20
nutThreephaseTable.nutThreephaseEntry.nutThreephaseVoltage.4.input.bypass.l1l2
= INTEGER: 398.3
nutThreephaseTable.nutThreephaseEntry.nutThreephaseVoltage.5.output.load.none
= INTEGER: 120.0
nutThreephaseTable.nutThreephaseEntry.nutThreephasePower.4.output.load.l1 =
INTEGER: 35700
nutThreephaseTable.nutThreephaseEntry.nutThreephasePowerfactor.4.output.load.none
= INTEGER: .82
nutThreephaseTable.nutThreephaseEntry.nutThreephaseFrequency.4.input.mains.none
= INTEGER: 50.0
nutThreephaseTable.nutThreephaseEntry.nutThreephaseFrequency.5.input.mains.none
= INTEGER: 50.1
nutThreephaseTable.nutThreephaseEntry.nutThreephaseFrequencyNominal.5.output.load.none
= INTEGER: 60.0

Where nutThreephaseTable.nutThreephaseEntry.nutThreephaseCurrent.5.output.load.none
is
equals to nutOutputTable.nutOutputEntry.nutOutputCurrent.5
I know that not every domain/context have all properties but it was just
easier to use a single table for every combination and letting the agent
answer the invalid
combination as absent.

Expect for nutThreephaseTable and nutOutletTable, I kept the same nut name
but used upcase letters instead of dots.
For outlets, I moved the outlet index (outlet.3.xxx) to outletIndex. For
3-phase, I moved the domain,subdomain,context to the index.


> a few notes:
> - I'm still thinking about the added value of repeating the collection
> name for each leaf.
> Ie device.deviceIndex Vs device.Index
> the first (your current approach) is probably more in phase with SNMP best
> practices,
>

I guess I already answered this. Object names are global. Generally they
are wrote in order to never conflict with the objects name declared in
other MIBs.


> - I'm still unsure on the right device index place.
> you have used leaf indexes (Ie, indexes are located on the leaf), while
> NUT native approach would more be "root" indexes.
> that would give something like
> NUT-MIB::[index].collection.data
>
>
Sorry, again, snmp fixed rules. The only think that can change in the
presence of an object (scalar or column) and the table index. Nothing more.


> so, for the 1rst NUT device:
> NUT-MIB::0.device.name
> NUT-MIB::0.device.model
> (...)
>
> the root would also contain:
> NUT-MIB::server.{info,version}
> and possibly a devices list, or at least a device count (Ie how many
> devices you can iterate on...)
>
> - for the 1rst round, I'm more interested in stabilizing the MIB tree, to
> have a stable *numeric* set.
> Exact string names have a lower priority.
>

Sure. You're right.


>
> - the "MAYBE" on commands (and settings) is to be removed.
> this is also part of the SNMP interface, though it should be used only in
> SNMP v3 mode (v1 is a security mess!).
>
>
I didn't get it.


> - part of the requirements, I'd like the agent to be able to work in
> standalone or sub agent mode.
>

After there is a defined mib, the agent could be written using net-snmp
libs. It would allow the agent to be an agentx and integrate into the
existing snmp service. The use of pass_persist is very simple but maybe not
the long-term way to go.


>
> so, to conclude this first mail, you did a great work!
> I've sadly not enough time to fully enter the loop with you.
> but I'm willing to devote all the support and time I can to push and
> complete this effort (including IANA process).
> on your side, are you willing to continue (and maybe enter the NUT team
> for the long run) or just to contribute what you did so far?
>

I'm willing to contribute for the SNMP agent. If it gets up and running, I
might even remove the need of a NUT protocol or even the NUT client.
upsc could be rewritten into a snmp client. Commands could use the SNMP SET
operation. Events could use the snmptrap to send thinks like "forced
shutdown" to the clients (although
snmptrap are more unicast communication than broadcast one).  If this
succeeds, the client of an NUT server could be any SNMP management software.
However, this is not my objective.

I'll try the dummy devices and I'll update the github repo sometime soon.

Cheers,


> cheers,
> Arnaud
> --
> Linux / Unix / Opensource Engineering Expert - Eaton -
> http://opensource.eaton.com
> Network UPS Tools (NUT) Project Leader - http://www.networkupstools.org
> Debian Developer - http://www.debian.org
> Free Software Developer - http://arnaud.quette.free.fr
>
>
---
     Luiz Angelo Daros de Luca, Me.
            luizluca at gmail.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.alioth.debian.org/pipermail/nut-upsdev/attachments/20120623/905e99d9/attachment-0001.html>


More information about the Nut-upsdev mailing list