diff --git a/plugins/inputs/ipmi_sensor/README.md b/plugins/inputs/ipmi_sensor/README.md index 64361950d676a..3b6c22c6f7c86 100644 --- a/plugins/inputs/ipmi_sensor/README.md +++ b/plugins/inputs/ipmi_sensor/README.md @@ -47,13 +47,14 @@ they're configured: # privilege = "ADMINISTRATOR" ## ## optionally specify one or more servers via a url matching - ## [username[:password]@][protocol[(address)]] + ## [username[:password]@][protocol[(address),hostname]] ## e.g. - ## root:passwd@lan(127.0.0.1) + ## root:passwd@lan(127.0.0.1),example_host ## + ## The 'hostname' is an optional tag that can be used to identify the server with a human-readable name. ## if no servers are specified, local machine sensor stats will be queried ## - # servers = ["USERID:PASSW0RD@lan(192.168.1.1)"] + # v = ["USERID:PASSW0RD@lan(192.168.1.1),example_host"] ## Recommended: use metric 'interval' that is a multiple of 'timeout' to avoid ## gaps or overlap in pulled data @@ -88,7 +89,8 @@ Version 1 schema: - unit - host - server (only when retrieving stats from remote servers) - - fields: + - hostname (only when specifying hostname in config) + - fields:d - status (int, 1=ok status_code/0=anything else) - value (float) @@ -103,13 +105,14 @@ Version 2 schema: - unit (only on analog values) - host - server (only when retrieving stats from remote) + - hostname (only when specifying hostname in config) - fields: - value (float) ### Permissions When gathering from the local system, Telegraf will need permission to the -ipmi device node. When using udev you can create the device node giving +ipmi device node. When using udev you can create the device node giving `rw` permissions to the `telegraf` user by adding the following rule to `/etc/udev/rules.d/52-telegraf-ipmi.rules`: @@ -150,6 +153,17 @@ ipmi_sensor,server=10.20.2.203,name=power_supplies value=0,status=1i 15171255130 ipmi_sensor,server=10.20.2.203,name=fan_1,unit=percent status=1i,value=43.12 1517125513000000000 ``` +When retrieving stats from a remote server(hostname specified): + +```shell +ipmi_sensor,server=10.20.2.203,hostname=example_host,name=uid_light value=0,status=1i 1517125513000000000 +ipmi_sensor,server=10.20.2.203,hostname=example_host,name=sys._health_led status=1i,value=0 1517125513000000000 +ipmi_sensor,server=10.20.2.203,hostname=example_host,name=power_supply_1,unit=watts status=1i,value=110 1517125513000000000 +ipmi_sensor,server=10.20.2.203,hostname=example_host,name=power_supply_2,unit=watts status=1i,value=120 1517125513000000000 +ipmi_sensor,server=10.20.2.203,hostname=example_host,name=power_supplies value=0,status=1i 1517125513000000000 +ipmi_sensor,server=10.20.2.203,hostname=example_host,name=fan_1,unit=percent status=1i,value=43.12 1517125513000000000 +``` + When retrieving stats from the local machine (no server specified): ```shell diff --git a/plugins/inputs/ipmi_sensor/connection.go b/plugins/inputs/ipmi_sensor/connection.go index b67ba06b9a619..d7438677f4cc4 100644 --- a/plugins/inputs/ipmi_sensor/connection.go +++ b/plugins/inputs/ipmi_sensor/connection.go @@ -16,6 +16,7 @@ type Connection struct { Interface string Privilege string HexKey string + IpmiIP string } func NewConnection(server, privilege, hexKey string) *Connection { @@ -43,7 +44,7 @@ func NewConnection(server, privilege, hexKey string) *Connection { inx3 := strings.Index(connstr, ")") conn.Interface = connstr[0:inx2] - conn.Hostname = connstr[inx2+1 : inx3] + conn.IpmiIP = connstr[inx2+1 : inx3] } return conn @@ -56,7 +57,7 @@ func (c *Connection) options() []string { } options := []string{ - "-H", c.Hostname, + "-H", c.IpmiIP, "-U", c.Username, "-P", c.Password, "-I", intf, @@ -76,22 +77,22 @@ func (c *Connection) options() []string { // RemoteIP returns the remote (bmc) IP address of the Connection func (c *Connection) RemoteIP() string { - if net.ParseIP(c.Hostname) == nil { - addrs, err := net.LookupHost(c.Hostname) + if net.ParseIP(c.IpmiIP) == nil { + addrs, err := net.LookupHost(c.IpmiIP) if err != nil && len(addrs) > 0 { return addrs[0] } } - return c.Hostname + return c.IpmiIP } // LocalIP returns the local (client) IP address of the Connection func (c *Connection) LocalIP() string { - conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", c.Hostname, c.Port)) + conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", c.IpmiIP, c.Port)) if err != nil { // don't bother returning an error, since this value will never // make it to the bmc if we can't connect to it. - return c.Hostname + return c.IpmiIP } _ = conn.Close() host, _, _ := net.SplitHostPort(conn.LocalAddr().String()) diff --git a/plugins/inputs/ipmi_sensor/connection_test.go b/plugins/inputs/ipmi_sensor/connection_test.go index 3be902e3264bc..f2e33b7e83d73 100644 --- a/plugins/inputs/ipmi_sensor/connection_test.go +++ b/plugins/inputs/ipmi_sensor/connection_test.go @@ -12,9 +12,9 @@ func TestNewConnection(t *testing.T) { con *Connection }{ { - "USERID:PASSW0RD@lan(192.168.1.1)", + "USERID:PASSW0RD@lan(192.168.1.1),host", &Connection{ - Hostname: "192.168.1.1", + IpmiIP: "192.168.1.1", Username: "USERID", Password: "PASSW0RD", Interface: "lan", @@ -23,9 +23,9 @@ func TestNewConnection(t *testing.T) { }, }, { - "USERID:PASS:!@#$%^&*(234)_+W0RD@lan(192.168.1.1)", + "USERID:PASS:!@#$%^&*(234)_+W0RD@lan(192.168.1.1),", &Connection{ - Hostname: "192.168.1.1", + IpmiIP: "192.168.1.1", Username: "USERID", Password: "PASS:!@#$%^&*(234)_+W0RD", Interface: "lan", @@ -37,7 +37,7 @@ func TestNewConnection(t *testing.T) { { "USERID@PASSW0RD@lan(192.168.1.1)", &Connection{ - Hostname: "192.168.1.1", + IpmiIP: "192.168.1.1", Username: "", Password: "", Interface: "lan", @@ -59,7 +59,7 @@ func TestGetCommandOptions(t *testing.T) { }{ { &Connection{ - Hostname: "192.168.1.1", + IpmiIP: "192.168.1.1", Username: "user", Password: "password", Interface: "lan", @@ -70,7 +70,7 @@ func TestGetCommandOptions(t *testing.T) { }, { &Connection{ - Hostname: "192.168.1.1", + IpmiIP: "192.168.1.1", Username: "user", Password: "password", Interface: "lan", diff --git a/plugins/inputs/ipmi_sensor/ipmi_sensor.go b/plugins/inputs/ipmi_sensor/ipmi_sensor.go index ade0a7f19af14..994e9b054148b 100644 --- a/plugins/inputs/ipmi_sensor/ipmi_sensor.go +++ b/plugins/inputs/ipmi_sensor/ipmi_sensor.go @@ -22,6 +22,7 @@ import ( ) // DO NOT REMOVE THE NEXT TWO LINES! This is required to embed the sampleConfig data. +// //go:embed sample.conf var sampleConfig string @@ -106,10 +107,16 @@ func (m *Ipmi) Gather(acc telegraf.Accumulator) error { func (m *Ipmi) parse(acc telegraf.Accumulator, server string) error { opts := make([]string, 0) + ipmiIP := "" hostname := "" if server != "" { + lastIndex := strings.LastIndex(server, ",") + if lastIndex > 1 && server[lastIndex+1:] != "" { + hostname = strings.TrimSpace(server[lastIndex+1:]) + } + conn := NewConnection(server, m.Privilege, m.HexKey) - hostname = conn.Hostname + ipmiIP = conn.IpmiIP opts = conn.options() } opts = append(opts, "sdr") @@ -152,12 +159,12 @@ func (m *Ipmi) parse(acc telegraf.Accumulator, server string) error { return fmt.Errorf("failed to run command %s: %s - %s", strings.Join(sanitizeIPMICmd(cmd.Args), " "), err, string(out)) } if m.MetricVersion == 2 { - return m.parseV2(acc, hostname, out, timestamp) + return m.parseV2(acc, ipmiIP, hostname, out, timestamp) } - return m.parseV1(acc, hostname, out, timestamp) + return m.parseV1(acc, ipmiIP, hostname, out, timestamp) } -func (m *Ipmi) parseV1(acc telegraf.Accumulator, hostname string, cmdOut []byte, measuredAt time.Time) error { +func (m *Ipmi) parseV1(acc telegraf.Accumulator, ipmiIP string, hostname string, cmdOut []byte, measuredAt time.Time) error { // each line will look something like // Planar VBAT | 3.05 Volts | ok scanner := bufio.NewScanner(bytes.NewReader(cmdOut)) @@ -172,8 +179,11 @@ func (m *Ipmi) parseV1(acc telegraf.Accumulator, hostname string, cmdOut []byte, } // tag the server is we have one + if ipmiIP != "" { + tags["server"] = ipmiIP + } if hostname != "" { - tags["server"] = hostname + tags["hostname"] = hostname } fields := make(map[string]interface{}) @@ -184,7 +194,6 @@ func (m *Ipmi) parseV1(acc telegraf.Accumulator, hostname string, cmdOut []byte, } description := ipmiFields["description"] - // handle hex description field if strings.HasPrefix(description, "0x") { descriptionInt, err := strconv.ParseInt(description, 0, 64) @@ -214,7 +223,7 @@ func (m *Ipmi) parseV1(acc telegraf.Accumulator, hostname string, cmdOut []byte, return scanner.Err() } -func (m *Ipmi) parseV2(acc telegraf.Accumulator, hostname string, cmdOut []byte, measuredAt time.Time) error { +func (m *Ipmi) parseV2(acc telegraf.Accumulator, ipmiIP string, hostname string, cmdOut []byte, measuredAt time.Time) error { // each line will look something like // CMOS Battery | 65h | ok | 7.1 | // Temp | 0Eh | ok | 3.1 | 55 degrees C @@ -230,9 +239,12 @@ func (m *Ipmi) parseV2(acc telegraf.Accumulator, hostname string, cmdOut []byte, "name": transform(ipmiFields["name"]), } - // tag the server is we have one + // tag the server is we have oneserver + if ipmiIP != "" { + tags["server"] = ipmiIP + } if hostname != "" { - tags["server"] = hostname + tags["hostname"] = hostname } tags["entity_id"] = transform(ipmiFields["entity_id"]) tags["status_code"] = trim(ipmiFields["status_code"]) diff --git a/plugins/inputs/ipmi_sensor/ipmi_sensor_test.go b/plugins/inputs/ipmi_sensor/ipmi_sensor_test.go index 559783fb64e3e..386a98b72247e 100644 --- a/plugins/inputs/ipmi_sensor/ipmi_sensor_test.go +++ b/plugins/inputs/ipmi_sensor/ipmi_sensor_test.go @@ -16,7 +16,7 @@ import ( func TestGather(t *testing.T) { i := &Ipmi{ - Servers: []string{"USERID:PASSW0RD@lan(192.168.1.1)"}, + Servers: []string{"USERID:PASSW0,RD@lan(192.168.1.1),test-host"}, Path: "ipmitool", Privilege: "USER", Timeout: config.Duration(time.Second * 5), @@ -47,9 +47,10 @@ func TestGather(t *testing.T) { "status": 1, }, map[string]string{ - "name": "ambient_temp", - "server": "192.168.1.1", - "unit": "degrees_c", + "name": "ambient_temp", + "server": "192.168.1.1", + "unit": "degrees_c", + "hostname": "test-host", }, }, { @@ -58,9 +59,10 @@ func TestGather(t *testing.T) { "status": 1, }, map[string]string{ - "name": "altitude", - "server": "192.168.1.1", - "unit": "feet", + "name": "altitude", + "server": "192.168.1.1", + "unit": "feet", + "hostname": "test-host", }, }, { @@ -69,9 +71,10 @@ func TestGather(t *testing.T) { "status": 1, }, map[string]string{ - "name": "avg_power", - "server": "192.168.1.1", - "unit": "watts", + "name": "avg_power", + "server": "192.168.1.1", + "unit": "watts", + "hostname": "test-host", }, }, { @@ -80,9 +83,10 @@ func TestGather(t *testing.T) { "status": 1, }, map[string]string{ - "name": "planar_5v", - "server": "192.168.1.1", - "unit": "volts", + "name": "planar_5v", + "server": "192.168.1.1", + "unit": "volts", + "hostname": "test-host", }, }, { @@ -91,9 +95,10 @@ func TestGather(t *testing.T) { "status": 1, }, map[string]string{ - "name": "planar_vbat", - "server": "192.168.1.1", - "unit": "volts", + "name": "planar_vbat", + "server": "192.168.1.1", + "unit": "volts", + "hostname": "test-host", }, }, { @@ -102,9 +107,10 @@ func TestGather(t *testing.T) { "status": 1, }, map[string]string{ - "name": "fan_1a_tach", - "server": "192.168.1.1", - "unit": "rpm", + "name": "fan_1a_tach", + "server": "192.168.1.1", + "unit": "rpm", + "hostname": "test-host", }, }, { @@ -113,9 +119,10 @@ func TestGather(t *testing.T) { "status": 1, }, map[string]string{ - "name": "fan_1b_tach", - "server": "192.168.1.1", - "unit": "rpm", + "name": "fan_1b_tach", + "server": "192.168.1.1", + "unit": "rpm", + "hostname": "test-host", }, }, } @@ -390,7 +397,7 @@ OS RealTime Mod | 0x00 | ok func TestGatherV2(t *testing.T) { i := &Ipmi{ - Servers: []string{"USERID:PASSW0RD@lan(192.168.1.1)"}, + Servers: []string{"USERID:PASSW0RD@lan(192.168.1.1),test-host"}, Path: "ipmitool", Privilege: "USER", Timeout: config.Duration(time.Second * 5), @@ -425,6 +432,7 @@ func TestGatherV2(t *testing.T) { "status_code": "ns", "status_desc": "no_reading", "server": "192.168.1.1", + "hostname": "test-host", }, }, } @@ -632,6 +640,7 @@ Power Supply 1 | 03h | ok | 10.1 | 110 Watts, Presence detected func Test_parseV1(t *testing.T) { type args struct { + ipmiIP string hostname string cmdOut []byte measuredAt time.Time @@ -645,6 +654,7 @@ func Test_parseV1(t *testing.T) { { name: "Test correct V1 parsing with hex code", args: args{ + ipmiIP: "10.20.2.1", hostname: "host", measuredAt: time.Now(), cmdOut: []byte("PS1 Status | 0x02 | ok"), @@ -655,6 +665,7 @@ func Test_parseV1(t *testing.T) { { name: "Test correct V1 parsing with value with unit", args: args{ + ipmiIP: "10.20.2.1", hostname: "host", measuredAt: time.Now(), cmdOut: []byte("Avg Power | 210 Watts | ok"), @@ -672,7 +683,7 @@ func Test_parseV1(t *testing.T) { t.Run(tt.name, func(t *testing.T) { var acc testutil.Accumulator - if err := ipmi.parseV1(&acc, tt.args.hostname, tt.args.cmdOut, tt.args.measuredAt); (err != nil) != tt.wantErr { + if err := ipmi.parseV1(&acc, tt.args.ipmiIP, tt.args.hostname, tt.args.cmdOut, tt.args.measuredAt); (err != nil) != tt.wantErr { t.Errorf("parseV1() error = %v, wantErr %v", err, tt.wantErr) } @@ -683,6 +694,7 @@ func Test_parseV1(t *testing.T) { func Test_parseV2(t *testing.T) { type args struct { + ipmiIP string hostname string cmdOut []byte measuredAt time.Time @@ -696,6 +708,7 @@ func Test_parseV2(t *testing.T) { { name: "Test correct V2 parsing with analog value with unit", args: args{ + ipmiIP: "10.20.2.1", hostname: "host", cmdOut: []byte("Power Supply 1 | 03h | ok | 10.1 | 110 Watts, Presence detected"), measuredAt: time.Now(), @@ -705,7 +718,8 @@ func Test_parseV2(t *testing.T) { map[string]string{ "name": "power_supply_1", "status_code": "ok", - "server": "host", + "server": "10.20.2.1", + "hostname": "host", "entity_id": "10.1", "unit": "watts", "status_desc": "presence_detected", @@ -719,6 +733,7 @@ func Test_parseV2(t *testing.T) { { name: "Test correct V2 parsing without analog value", args: args{ + ipmiIP: "10.20.2.1", hostname: "host", cmdOut: []byte("Intrusion | 73h | ok | 7.1 |"), measuredAt: time.Now(), @@ -728,7 +743,8 @@ func Test_parseV2(t *testing.T) { map[string]string{ "name": "intrusion", "status_code": "ok", - "server": "host", + "server": "10.20.2.1", + "hostname": "host", "entity_id": "7.1", "status_desc": "ok", }, @@ -741,6 +757,7 @@ func Test_parseV2(t *testing.T) { { name: "parse negative value", args: args{ + ipmiIP: "10.20.2.1", hostname: "host", cmdOut: []byte("DIMM Thrm Mrgn 1 | B0h | ok | 8.1 | -55 degrees C"), measuredAt: time.Now(), @@ -750,7 +767,8 @@ func Test_parseV2(t *testing.T) { map[string]string{ "name": "dimm_thrm_mrgn_1", "status_code": "ok", - "server": "host", + "server": "10.20.2.1", + "hostname": "host", "entity_id": "8.1", "unit": "degrees_c", }, @@ -769,7 +787,7 @@ func Test_parseV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var acc testutil.Accumulator - if err := ipmi.parseV2(&acc, tt.args.hostname, tt.args.cmdOut, tt.args.measuredAt); (err != nil) != tt.wantErr { + if err := ipmi.parseV2(&acc, tt.args.ipmiIP, tt.args.hostname, tt.args.cmdOut, tt.args.measuredAt); (err != nil) != tt.wantErr { t.Errorf("parseV2() error = %v, wantErr %v", err, tt.wantErr) } testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) diff --git a/plugins/inputs/ipmi_sensor/sample.conf b/plugins/inputs/ipmi_sensor/sample.conf index 3cabeb204d9f9..e1a1eb5a37f47 100644 --- a/plugins/inputs/ipmi_sensor/sample.conf +++ b/plugins/inputs/ipmi_sensor/sample.conf @@ -12,13 +12,14 @@ # privilege = "ADMINISTRATOR" ## ## optionally specify one or more servers via a url matching - ## [username[:password]@][protocol[(address)]] + ## [username[:password]@][protocol[(address),hostname]] ## e.g. - ## root:passwd@lan(127.0.0.1) + ## root:passwd@lan(127.0.0.1),example-host ## + ## The 'hostname' is an optional field that can be used to identify the server with a human-readable name. ## if no servers are specified, local machine sensor stats will be queried ## - # servers = ["USERID:PASSW0RD@lan(192.168.1.1)"] + # servers = ["USERID:PASSW0RD@lan(192.168.1.1),example-host"] ## Recommended: use metric 'interval' that is a multiple of 'timeout' to avoid ## gaps or overlap in pulled data