Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ebookbug ipmi lowercase change #919

Merged
merged 2 commits into from
Mar 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"strings"
"time"
"unicode"
)

const alphanum string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
Expand Down Expand Up @@ -122,6 +123,23 @@ func GetTLSConfig(
return t, nil
}

// SnakeCase converts the given string to snake case following the Golang format:
// acronyms are converted to lower-case and preceded by an underscore.
func SnakeCase(in string) string {
runes := []rune(in)
length := len(runes)

var out []rune
for i := 0; i < length; i++ {
if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
out = append(out, '_')
}
out = append(out, unicode.ToLower(runes[i]))
}

return string(out)
}

// Glob will test a string pattern, potentially containing globs, against a
// subject string. The result is a simple true/false, determining whether or
// not the glob pattern matched the subject text.
Expand Down
29 changes: 29 additions & 0 deletions internal/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,32 @@ func TestGlob(t *testing.T) {
testGlobNoMatch(t, pattern, "this_is_a_test")
}
}

type SnakeTest struct {
input string
output string
}

var tests = []SnakeTest{
{"a", "a"},
{"snake", "snake"},
{"A", "a"},
{"ID", "id"},
{"MOTD", "motd"},
{"Snake", "snake"},
{"SnakeTest", "snake_test"},
{"APIResponse", "api_response"},
{"SnakeID", "snake_id"},
{"SnakeIDGoogle", "snake_id_google"},
{"LinuxMOTD", "linux_motd"},
{"OMGWTFBBQ", "omgwtfbbq"},
{"omg_wtf_bbq", "omg_wtf_bbq"},
}

func TestSnakeCase(t *testing.T) {
for _, test := range tests {
if SnakeCase(test.input) != test.output {
t.Errorf(`SnakeCase("%s"), wanted "%s", got \%s"`, test.input, test.output, SnakeCase(test.input))
}
}
}
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
_ "github.com/influxdata/telegraf/plugins/inputs/httpjson"
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
_ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor"
_ "github.com/influxdata/telegraf/plugins/inputs/jolokia"
_ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer"
_ "github.com/influxdata/telegraf/plugins/inputs/leofs"
Expand Down
42 changes: 42 additions & 0 deletions plugins/inputs/ipmi_sensor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 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 lan -H 192.168.1.1 -U USERID -P PASSW0RD sdr

## Measurements

- ipmi_sensor:

* Tags: `name`, `server`, `unit`
* Fields:
- status
- value

## Configuration

```toml
[[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,server=10.20.2.203,unit=degrees_c,name=ambient_temp status=1i,value=20 1458488465012559455
> ipmi_sensor,server=10.20.2.203,unit=feet,name=altitude status=1i,value=80 1458488465012688613
> ipmi_sensor,server=10.20.2.203,unit=watts,name=avg_power status=1i,value=220 1458488465012776511
> ipmi_sensor,server=10.20.2.203,unit=volts,name=planar_3.3v status=1i,value=3.28 1458488465012861875
> ipmi_sensor,server=10.20.2.203,unit=volts,name=planar_vbat status=1i,value=3.04 1458488465013072508
> ipmi_sensor,server=10.20.2.203,unit=rpm,name=fan_1a_tach status=1i,value=2610 1458488465013137932
> ipmi_sensor,server=10.20.2.203,unit=rpm,name=fan_1b_tach status=1i,value=1775 1458488465013279896
```
38 changes: 38 additions & 0 deletions plugins/inputs/ipmi_sensor/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ipmi_sensor

import (
"bytes"
"fmt"
"os/exec"
"strings"
)

type CommandRunner struct{}

func (t CommandRunner) cmd(conn *Connection, args ...string) *exec.Cmd {
path := conn.Path
opts := append(conn.options(), args...)

if path == "" {
path = "ipmitool"
}

return exec.Command(path, opts...)

}

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
cmd.Stderr = &stderr

err := cmd.Run()
if err != nil {
return "", fmt.Errorf("run %s %s: %s (%s)",
cmd.Path, strings.Join(cmd.Args, " "), stderr.String(), err)
}

return stdout.String(), err
}
89 changes: 89 additions & 0 deletions plugins/inputs/ipmi_sensor/connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package ipmi_sensor

import (
"fmt"
"net"
"strconv"
"strings"
)

// Connection properties for a Client
type Connection struct {
Hostname string
Username string
Password string
Path string
Port int
Interface string
}

func NewConnection(server string) *Connection {
conn := &Connection{}
inx1 := strings.Index(server, "@")
inx2 := strings.Index(server, "(")
inx3 := strings.Index(server, ")")

connstr := server

if inx1 > 0 {
security := server[0:inx1]
connstr = server[inx1+1 : len(server)]
up := strings.Split(security, ":")
conn.Username = up[0]
conn.Password = up[1]
}

if inx2 > 0 {
inx2 = strings.Index(connstr, "(")
inx3 = strings.Index(connstr, ")")

conn.Interface = connstr[0:inx2]
conn.Hostname = connstr[inx2+1 : inx3]
}

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 {
addrs, err := net.LookupHost(c.Hostname)
if err != nil && len(addrs) > 0 {
return addrs[0]
}
}
return c.Hostname
}

// 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))
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
}
_ = conn.Close()
host, _, _ := net.SplitHostPort(conn.LocalAddr().String())
return host
}
129 changes: 129 additions & 0 deletions plugins/inputs/ipmi_sensor/ipmi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package ipmi_sensor

import (
"strconv"
"strings"
"time"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

type Ipmi struct {
Servers []string
runner Runner
}

var sampleConfig = `
## specify servers via a url matching:
## [username[:password]@][protocol[(address)]]
## e.g.
## root:passwd@lan(127.0.0.1)
##
servers = ["USERID:PASSW0RD@lan(192.168.1.1)"]
`

func NewIpmi() *Ipmi {
return &Ipmi{
runner: CommandRunner{},
}
}

func (m *Ipmi) SampleConfig() string {
return sampleConfig
}

func (m *Ipmi) Description() string {
return "Read metrics from one or many bare metal servers"
}

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 {
return err
}
}

return nil
}

func (m *Ipmi) gatherServer(serv string, acc telegraf.Accumulator) error {
conn := NewConnection(serv)

res, err := m.runner.Run(conn, "sdr")
if err != nil {
return err
}

// each line will look something like
// Planar VBAT | 3.05 Volts | ok
lines := strings.Split(res, "\n")
for i := 0; i < len(lines); i++ {
vals := strings.Split(lines[i], "|")
if len(vals) != 3 {
continue
}

tags := map[string]string{
"server": conn.Hostname,
"name": transform(vals[0]),
}

fields := make(map[string]interface{})
if strings.EqualFold("ok", trim(vals[2])) {
fields["status"] = 1
} else {
fields["status"] = 0
}

val1 := trim(vals[1])

if strings.Index(val1, " ") > 0 {
// split middle column into value and unit
valunit := strings.SplitN(val1, " ", 2)
fields["value"] = Atofloat(valunit[0])
if len(valunit) > 1 {
tags["unit"] = transform(valunit[1])
}
} else {
fields["value"] = 0.0
}

acc.AddFields("ipmi_sensor", fields, tags, time.Now())
}

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 {
return 0.0
} else {
return f
}
}

func trim(s string) string {
return strings.TrimSpace(s)
}

func transform(s string) string {
s = trim(s)
s = strings.ToLower(s)
return strings.Replace(s, " ", "_", -1)
}

func init() {
inputs.Add("ipmi_sensor", func() telegraf.Input {
return &Ipmi{}
})
}
Loading