From 2551b7fc3b1b3ce1bb05a1d4a7d1715df75ed6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=85=89=E6=9D=83?= Date: Sun, 20 Mar 2016 23:54:17 +0800 Subject: [PATCH] Add unit test and read me document --- plugins/inputs/ipmi/README.md | 47 ++++++++ plugins/inputs/ipmi/command.go | 35 +----- plugins/inputs/ipmi/connection.go | 21 ++++ plugins/inputs/ipmi/ipmi.go | 17 ++- plugins/inputs/ipmi/ipmi_test.go | 189 ++++++++++++++++++++++++++++++ 5 files changed, 278 insertions(+), 31 deletions(-) create mode 100644 plugins/inputs/ipmi/README.md create mode 100644 plugins/inputs/ipmi/ipmi_test.go diff --git a/plugins/inputs/ipmi/README.md b/plugins/inputs/ipmi/README.md new file mode 100644 index 000000000..4e7b64718 --- /dev/null +++ b/plugins/inputs/ipmi/README.md @@ -0,0 +1,47 @@ +# Telegraf ipmi plugin + +Get bare metal metrics using the command line utility `ipmitool` + +see ipmitool(https://sourceforge.net/projects/ipmitool/files/ipmitool/) + +The plugin will use the following command to collect remote host sensor stats: + +ipmitool -I lanplus -H 192.168.1.1 -U USERID -P PASSW0RD sdr + +## Measurements + +- ipmi_sensor: + + * Tags: `host`,`inst` + * Fields: + - status + - value +## Configuration + +[[inputs.ipmi]] + ## specify servers via a url matching: + ## [username[:password]@][protocol[(address)]] + ## e.g. + ## root:passwd@lan(127.0.0.1) + ## + servers = ["USERID:PASSW0RD@lan(10.20.2.203)"] + +## Output + +> ipmi_sensor,host=10.20.2.203,inst=Ambient\ Temp status=1i,value=20 1458488465012559455 +> ipmi_sensor,host=10.20.2.203,inst=Altitude status=1i,value=80 1458488465012688613 +> ipmi_sensor,host=10.20.2.203,inst=Avg\ Power status=1i,value=220 1458488465012776511 +> ipmi_sensor,host=10.20.2.203,inst=Planar\ 3.3V status=1i,value=3.28 1458488465012861875 +> ipmi_sensor,host=10.20.2.203,inst=Planar\ 5V status=1i,value=4.9 1458488465012944188 +> ipmi_sensor,host=10.20.2.203,inst=Planar\ 12V status=1i,value=12.04 1458488465013008485 +> ipmi_sensor,host=10.20.2.203,inst=Planar\ VBAT status=1i,value=3.04 1458488465013072508 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 1A\ Tach status=1i,value=2610 1458488465013137932 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 1B\ Tach status=1i,value=1775 1458488465013279896 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 2A\ Tach status=1i,value=1972 1458488465013358177 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 2B\ Tach status=1i,value=1275 1458488465013434023 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 3A\ Tach status=1i,value=2929 1458488465013514567 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 3B\ Tach status=1i,value=2125 1458488465013582616 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 1 status=1i,value=0 1458488465013643746 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 2 status=1i,value=0 1458488465013714887 +> ipmi_sensor,host=10.20.2.203,inst=Fan\ 3 status=1i,value=0 1458488465013861854 + diff --git a/plugins/inputs/ipmi/command.go b/plugins/inputs/ipmi/command.go index 949b20b0a..4b3a2e81b 100644 --- a/plugins/inputs/ipmi/command.go +++ b/plugins/inputs/ipmi/command.go @@ -5,37 +5,14 @@ import ( "bytes" "fmt" "os/exec" - "strconv" "strings" ) -type IpmiCommand struct { - *Connection -} +type CommandRunner struct{} -func (t *IpmiCommand) options() []string { - intf := t.Interface - if intf == "" { - intf = "lanplus" - } - - options := []string{ - "-H", t.Hostname, - "-U", t.Username, - "-P", t.Password, - "-I", intf, - } - - if t.Port != 0 { - options = append(options, "-p", strconv.Itoa(t.Port)) - } - - return options -} - -func (t *IpmiCommand) cmd(args ...string) *exec.Cmd { - path := t.Path - opts := append(t.options(), args...) +func (t CommandRunner) cmd(conn *Connection, args ...string) *exec.Cmd { + path := conn.Path + opts := append(conn.options(), args...) if path == "" { path = "ipmitool" @@ -45,8 +22,8 @@ func (t *IpmiCommand) cmd(args ...string) *exec.Cmd { } -func (t *IpmiCommand) Run(args ...string) (string, error) { - cmd := t.cmd(args...) +func (t CommandRunner) Run(conn *Connection, args ...string) (string, error) { + cmd := t.cmd(conn, args...) var stdout bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &stdout diff --git a/plugins/inputs/ipmi/connection.go b/plugins/inputs/ipmi/connection.go index d1c090038..de555a4c4 100644 --- a/plugins/inputs/ipmi/connection.go +++ b/plugins/inputs/ipmi/connection.go @@ -4,6 +4,7 @@ package ipmi import ( "fmt" "net" + "strconv" "strings" ) @@ -44,6 +45,26 @@ func NewConnection(server string) *Connection { return conn } +func (t *Connection) options() []string { + intf := t.Interface + if intf == "" { + intf = "lan" + } + + options := []string{ + "-H", t.Hostname, + "-U", t.Username, + "-P", t.Password, + "-I", intf, + } + + if t.Port != 0 { + options = append(options, "-p", strconv.Itoa(t.Port)) + } + + return options +} + // RemoteIP returns the remote (bmc) IP address of the Connection func (c *Connection) RemoteIP() string { if net.ParseIP(c.Hostname) == nil { diff --git a/plugins/inputs/ipmi/ipmi.go b/plugins/inputs/ipmi/ipmi.go index 92fe769e6..03cd0c210 100644 --- a/plugins/inputs/ipmi/ipmi.go +++ b/plugins/inputs/ipmi/ipmi.go @@ -12,6 +12,7 @@ import ( type Ipmi struct { Servers []string + runner Runner } var sampleConfig = ` @@ -23,6 +24,12 @@ var sampleConfig = ` servers = ["USERID:PASSW0RD@lan(192.168.1.1)"] ` +func NewIpmi() *Ipmi { + return &Ipmi{ + runner: CommandRunner{}, + } +} + func (m *Ipmi) SampleConfig() string { return sampleConfig } @@ -32,6 +39,9 @@ func (m *Ipmi) Description() string { } func (m *Ipmi) Gather(acc telegraf.Accumulator) error { + if m.runner == nil { + m.runner = CommandRunner{} + } for _, serv := range m.Servers { err := m.gatherServer(serv, acc) if err != nil { @@ -45,8 +55,7 @@ func (m *Ipmi) Gather(acc telegraf.Accumulator) error { func (m *Ipmi) gatherServer(serv string, acc telegraf.Accumulator) error { conn := NewConnection(serv) - tool := &IpmiCommand{conn} - res, err := tool.Run("sdr") + res, err := m.runner.Run(conn, "sdr") if err != nil { return err } @@ -80,6 +89,10 @@ func (m *Ipmi) gatherServer(serv string, acc telegraf.Accumulator) error { return nil } +type Runner interface { + Run(conn *Connection, args ...string) (string, error) +} + func Atofloat(val string) float64 { f, err := strconv.ParseFloat(val, 64) if err != nil { diff --git a/plugins/inputs/ipmi/ipmi_test.go b/plugins/inputs/ipmi/ipmi_test.go new file mode 100644 index 000000000..a3f98e918 --- /dev/null +++ b/plugins/inputs/ipmi/ipmi_test.go @@ -0,0 +1,189 @@ +// ipmi_test +package ipmi + +import ( + "testing" + + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const serv = "USERID:PASSW0RD@lan(192.168.1.1)" + +const cmdReturn = ` +Ambient Temp | 20 degrees C | ok +Altitude | 80 feet | ok +Avg Power | 210 Watts | ok +Planar 3.3V | 3.29 Volts | ok +Planar 5V | 4.90 Volts | ok +Planar 12V | 12.04 Volts | ok +Planar VBAT | 3.05 Volts | ok +Fan 1A Tach | 2610 RPM | ok +Fan 1B Tach | 1775 RPM | ok +Fan 2A Tach | 2001 RPM | ok +Fan 2B Tach | 1275 RPM | ok +Fan 3A Tach | 2929 RPM | ok +Fan 3B Tach | 2125 RPM | ok +Fan 1 | 0x00 | ok +Fan 2 | 0x00 | ok +Fan 3 | 0x00 | ok +Front Panel | 0x00 | ok +Video USB | 0x00 | ok +DASD Backplane 1 | 0x00 | ok +SAS Riser | 0x00 | ok +PCI Riser 1 | 0x00 | ok +PCI Riser 2 | 0x00 | ok +CPU 1 | 0x00 | ok +CPU 2 | 0x00 | ok +All CPUs | 0x00 | ok +One of The CPUs | 0x00 | ok +IOH Temp Status | 0x00 | ok +CPU 1 OverTemp | 0x00 | ok +CPU 2 OverTemp | 0x00 | ok +CPU Fault Reboot | 0x00 | ok +Aux Log | 0x00 | ok +NMI State | 0x00 | ok +ABR Status | 0x00 | ok +Firmware Error | 0x00 | ok +PCIs | 0x00 | ok +CPUs | 0x00 | ok +DIMMs | 0x00 | ok +Sys Board Fault | 0x00 | ok +Power Supply 1 | 0x00 | ok +Power Supply 2 | 0x00 | ok +PS 1 Fan Fault | 0x00 | ok +PS 2 Fan Fault | 0x00 | ok +VT Fault | 0x00 | ok +Pwr Rail A Fault | 0x00 | ok +Pwr Rail B Fault | 0x00 | ok +Pwr Rail C Fault | 0x00 | ok +Pwr Rail D Fault | 0x00 | ok +Pwr Rail E Fault | 0x00 | ok +PS 1 Therm Fault | 0x00 | ok +PS 2 Therm Fault | 0x00 | ok +PS1 12V OV Fault | 0x00 | ok +PS2 12V OV Fault | 0x00 | ok +PS1 12V UV Fault | 0x00 | ok +PS2 12V UV Fault | 0x00 | ok +PS1 12V OC Fault | 0x00 | ok +PS2 12V OC Fault | 0x00 | ok +PS 1 VCO Fault | 0x00 | ok +PS 2 VCO Fault | 0x00 | ok +Power Unit | 0x00 | ok +Cooling Zone 1 | 0x00 | ok +Cooling Zone 2 | 0x00 | ok +Cooling Zone 3 | 0x00 | ok +Drive 0 | 0x00 | ok +Drive 1 | 0x00 | ok +Drive 2 | 0x00 | ok +Drive 3 | 0x00 | ok +Drive 4 | 0x00 | ok +Drive 5 | 0x00 | ok +Drive 6 | 0x00 | ok +Drive 7 | 0x00 | ok +Drive 8 | 0x00 | ok +Drive 9 | 0x00 | ok +Drive 10 | 0x00 | ok +Drive 11 | 0x00 | ok +Drive 12 | 0x00 | ok +Drive 13 | 0x00 | ok +Drive 14 | 0x00 | ok +Drive 15 | 0x00 | ok +All DIMMS | 0x00 | ok +One of the DIMMs | 0x00 | ok +DIMM 1 | 0x00 | ok +DIMM 2 | 0x00 | ok +DIMM 3 | 0x00 | ok +DIMM 4 | 0x00 | ok +DIMM 5 | 0x00 | ok +DIMM 6 | 0x00 | ok +DIMM 7 | 0x00 | ok +DIMM 8 | 0x00 | ok +DIMM 9 | 0x00 | ok +DIMM 10 | 0x00 | ok +DIMM 11 | 0x00 | ok +DIMM 12 | 0x00 | ok +DIMM 13 | 0x00 | ok +DIMM 14 | 0x00 | ok +DIMM 15 | 0x00 | ok +DIMM 16 | 0x00 | ok +DIMM 17 | 0x00 | ok +DIMM 18 | 0x00 | ok +DIMM 1 Temp | 0x00 | ok +DIMM 2 Temp | 0x00 | ok +DIMM 3 Temp | 0x00 | ok +DIMM 4 Temp | 0x00 | ok +DIMM 5 Temp | 0x00 | ok +DIMM 6 Temp | 0x00 | ok +DIMM 7 Temp | 0x00 | ok +DIMM 8 Temp | 0x00 | ok +DIMM 9 Temp | 0x00 | ok +DIMM 10 Temp | 0x00 | ok +DIMM 11 Temp | 0x00 | ok +DIMM 12 Temp | 0x00 | ok +DIMM 13 Temp | 0x00 | ok +DIMM 14 Temp | 0x00 | ok +DIMM 15 Temp | 0x00 | ok +DIMM 16 Temp | 0x00 | ok +DIMM 17 Temp | 0x00 | ok +DIMM 18 Temp | 0x00 | ok +PCI 1 | 0x00 | ok +PCI 2 | 0x00 | ok +PCI 3 | 0x00 | ok +PCI 4 | 0x00 | ok +All PCI Error | 0x00 | ok +One of PCI Error | 0x00 | ok +IPMI Watchdog | 0x00 | ok +Host Power | 0x00 | ok +DASD Backplane 2 | 0x00 | ok +DASD Backplane 3 | Not Readable | ns +DASD Backplane 4 | Not Readable | ns +Backup Memory | 0x00 | ok +Progress | 0x00 | ok +Planar Fault | 0x00 | ok +SEL Fullness | 0x00 | ok +PCI 5 | 0x00 | ok +OS RealTime Mod | 0x00 | ok +` + +type runnerMock struct { + out string + err error +} + +func newRunnerMock(out string, err error) Runner { + return &runnerMock{ + out: out, + err: err, + } +} + +func (r runnerMock) Run(conn *Connection, args ...string) (out string, err error) { + if r.err != nil { + return out, r.err + } + return r.out, nil +} + +func TestIpmi(t *testing.T) { + i := &Ipmi{ + Servers: []string{"USERID:PASSW0RD@lan(192.168.1.1)"}, + runner: newRunnerMock(cmdReturn, nil), + } + + var acc testutil.Accumulator + + err := i.Gather(&acc) + + require.NoError(t, err) + + assert.Equal(t, acc.NFields(), 2, "non-numeric measurements should be ignored") +} + +func TestIpmiConnection(t *testing.T) { + conn := NewConnection(serv) + assert.Equal(t, "USERID", conn.Username) + assert.Equal(t, "lan", conn.Interface) + +}