Skip to content

Commit

Permalink
Generic logging interface (#135)
Browse files Browse the repository at this point in the history
* use generic logging interface
  • Loading branch information
jacobweinstock authored Sep 30, 2020
1 parent da661ea commit 13026a9
Show file tree
Hide file tree
Showing 42 changed files with 1,745 additions and 1,966 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ Session.vim
tags
# Persistent undo
[._]*.un~

# vscode
*.code-workspace
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ env:
install:
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.31.0
before_script:
- golangci-lint run --no-config --disable-all --enable=vet --enable=gofmt --enable=gocyclo --enable=golint --enable=ineffassign --enable=misspell --enable=deadcode --tests=false ./...
- golangci-lint run --no-config --disable-all --enable=vet --enable=gofmt --enable=gocyclo --enable=golint --enable=ineffassign --enable=misspell --enable=deadcode --tests=false ./...
script:
- go test ./...
notifications:
Expand Down
31 changes: 22 additions & 9 deletions discover/discover.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package discover

import (
"context"
"os"

log "github.com/sirupsen/logrus"

"github.com/bmc-toolbox/bmclib/errors"
"github.com/bmc-toolbox/bmclib/internal/httpclient"
_ "github.com/bmc-toolbox/bmclib/logging" // this make possible to setup logging and properties at any stage
"github.com/bmc-toolbox/bmclib/logging"

"github.com/bmc-toolbox/bmclib/providers/dummy/ibmc"
"github.com/go-logr/logr"
)

const (
Expand All @@ -24,16 +25,19 @@ const (

// ScanAndConnect will scan the bmc trying to learn the device type and return a working connection.
func ScanAndConnect(host string, username string, password string, options ...Option) (bmcConnection interface{}, err error) {
log.WithFields(log.Fields{"step": "ScanAndConnect", "host": host}).Debug("detecting vendor")

opts := &Options{HintCallback: func(_ string) error { return nil }}
for _, optFn := range options {
optFn(opts)
}
if opts.Logger == nil {
// create a default logger
opts.Logger = logging.DefaultLogger()
}
opts.Logger.V(1).Info("detecting vendor", "step", "ScanAndConnect", "host", host)

// return a connection to our dummy device.
if os.Getenv("BMCLIB_TEST") == "1" {
log.WithFields(log.Fields{"step": "ScanAndConnect", "host": host}).Debug("returning connection to dummy ibmc device.")
opts.Logger.V(1).Info("returning connection to dummy ibmc device.", "step", "ScanAndConnect", "host", host)
bmc, err := ibmc.New(host, username, password)
return bmc, err
}
Expand All @@ -45,7 +49,7 @@ func ScanAndConnect(host string, username string, password string, options ...Op

var probe = Probe{client: client, username: username, password: password, host: host}

var devices = map[string]func() (interface{}, error){
var devices = map[string]func(context.Context, logr.Logger) (interface{}, error){
ProbeHpIlo: probe.hpIlo,
ProbeIdrac8: probe.idrac8,
ProbeIdrac9: probe.idrac9,
Expand Down Expand Up @@ -73,9 +77,9 @@ func ScanAndConnect(host string, username string, password string, options ...Op
for _, probeID := range order {
probeDevice := devices[probeID]

log.WithFields(log.Fields{"step": "ScanAndConnect", "host": host}).Debug("probing to identify device")
opts.Logger.V(1).Info("probing to identify device", "step", "ScanAndConnect", "host", host)

bmcConnection, err := probeDevice()
bmcConnection, err := probeDevice(opts.Context, opts.Logger)

// if the device didn't match continue to probe
if err != nil && (err == errors.ErrDeviceNotMatched) {
Expand All @@ -98,6 +102,7 @@ func ScanAndConnect(host string, username string, password string, options ...Op
return nil, errors.ErrVendorUnknown
}

// Options to pass in
type Options struct {
// Hint is a probe ID that hints which probe should be probed first.
Hint string
Expand All @@ -107,6 +112,8 @@ type Options struct {
// If your code persists the hint as "best effort", always return a nil error. Callback is
// synchronous.
HintCallback func(string) error
Logger logr.Logger
Context context.Context
}

// Option is part of the functional options pattern, see the `With*` functions and
Expand All @@ -121,6 +128,12 @@ func WithHintCallBack(fn func(string) error) Option {
return func(args *Options) { args.HintCallback = fn }
}

// WithLogger sets the Options.Logger option
func WithLogger(log logr.Logger) Option { return func(args *Options) { args.Logger = log } }

// WithContext sets the Options.Context option
func WithContext(ctx context.Context) Option { return func(args *Options) { args.Context = ctx } }

func swapProbe(order []string, hint string) {
// With so few elements and since `==` uses SIMD,
// looping is faster than having yet another hash map.
Expand Down
46 changes: 24 additions & 22 deletions discover/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package discover

import (
"bytes"
"context"
"encoding/xml"
"fmt"
"io"
Expand All @@ -18,7 +19,7 @@ import (
"github.com/bmc-toolbox/bmclib/providers/hp/c7000"
"github.com/bmc-toolbox/bmclib/providers/hp/ilo"
"github.com/bmc-toolbox/bmclib/providers/supermicro/supermicrox"
log "github.com/sirupsen/logrus"
"github.com/go-logr/logr"
)

var (
Expand All @@ -34,7 +35,7 @@ type Probe struct {
password string
}

func (p *Probe) hpIlo() (bmcConnection interface{}, err error) {
func (p *Probe) hpIlo(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {

resp, err := p.client.Get(fmt.Sprintf("https://%s/xmldata?item=all", p.host))
if err != nil {
Expand Down Expand Up @@ -68,7 +69,8 @@ func (p *Probe) hpIlo() (bmcConnection interface{}, err error) {

if iloXML.HSI != nil {
if strings.HasPrefix(iloXML.MP.Pn, "Integrated Lights-Out") {
return ilo.New(p.host, p.username, p.password)
log.V(1).Info("step", "ScanAndConnect", "host", p.host, "vendor", string(devices.HP), "msg", "it's a HP with iLo")
return ilo.New(ctx, p.host, p.username, p.password, log)
}

return bmcConnection, fmt.Errorf("it's an HP, but I cound't not identify the hardware type. Please verify")
Expand All @@ -78,7 +80,7 @@ func (p *Probe) hpIlo() (bmcConnection interface{}, err error) {
return bmcConnection, errors.ErrDeviceNotMatched
}

func (p *Probe) hpC7000() (bmcConnection interface{}, err error) {
func (p *Probe) hpC7000(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {

resp, err := p.client.Get(fmt.Sprintf("https://%s/xmldata?item=all", p.host))
if err != nil {
Expand Down Expand Up @@ -106,16 +108,16 @@ func (p *Probe) hpC7000() (bmcConnection interface{}, err error) {
}

if iloXMLC.Infra2 != nil {
log.WithFields(log.Fields{"step": "ScanAndConnect", "host": p.host, "vendor": devices.HP}).Debug("it's a chassis")
return c7000.New(p.host, p.username, p.password)
log.V(1).Info("step", "ScanAndConnect", "host", p.host, "vendor", string(devices.HP), "msg", "it's a chassis")
return c7000.New(ctx, p.host, p.username, p.password, log)
}

}
return bmcConnection, errors.ErrDeviceNotMatched
}

// hpCl100 attempts to identify a cloudline device
func (p *Probe) hpCl100() (bmcConnection interface{}, err error) {
func (p *Probe) hpCl100(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {

// HPE Cloudline CL100
resp, err := p.client.Get(fmt.Sprintf("https://%s/res/ok.png", p.host))
Expand All @@ -133,15 +135,15 @@ func (p *Probe) hpCl100() (bmcConnection interface{}, err error) {
}
// ensure the response we got included a png
if resp.StatusCode == 200 && bytes.Contains(firstBytes, []byte("PNG")) {
log.WithFields(log.Fields{"step": "ScanAndConnect", "host": p.host, "vendor": devices.Cloudline}).Debug("it's a discrete")
log.V(1).Info("step", "ScanAndConnect", "host", p.host, "vendor", string(devices.Cloudline), "msg", "it's a discrete")
return bmcConnection, errors.NewErrUnsupportedHardware("hpe cl100 not supported")
}

return bmcConnection, errors.ErrDeviceNotMatched

}

func (p *Probe) idrac8() (bmcConnection interface{}, err error) {
func (p *Probe) idrac8(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {

resp, err := p.client.Get(fmt.Sprintf("https://%s/session?aimGetProp=hostname,gui_str_title_bar,OEMHostName,fwVersion,sysDesc", p.host))
if err != nil {
Expand All @@ -157,14 +159,14 @@ func (p *Probe) idrac8() (bmcConnection interface{}, err error) {
}

if resp.StatusCode == 200 && containsAnySubStr(payload, idrac8SysDesc) {
log.WithFields(log.Fields{"step": "connection", "host": p.host, "vendor": devices.Dell}).Debug("it's a idrac8")
return idrac8.New(p.host, p.username, p.password)
log.V(1).Info("step", "connection", "host", p.host, "vendor", string(devices.Dell), "msg", "it's a idrac8")
return idrac8.New(ctx, p.host, p.username, p.password, log)
}

return bmcConnection, errors.ErrDeviceNotMatched
}

func (p *Probe) idrac9() (bmcConnection interface{}, err error) {
func (p *Probe) idrac9(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {

resp, err := p.client.Get(fmt.Sprintf("https://%s/sysmgmt/2015/bmc/info", p.host))
if err != nil {
Expand All @@ -180,14 +182,14 @@ func (p *Probe) idrac9() (bmcConnection interface{}, err error) {
}

if resp.StatusCode == 200 && containsAnySubStr(payload, idrac9SysDesc) {
log.WithFields(log.Fields{"step": "connection", "host": p.host, "vendor": devices.Dell}).Debug("it's a idrac9")
return idrac9.New(p.host, p.username, p.password)
log.V(1).Info("step", "connection", "host", p.host, "vendor", string(devices.Dell), "msg", "it's a idrac9")
return idrac9.New(ctx, p.host, p.username, p.password, log)
}

return bmcConnection, errors.ErrDeviceNotMatched
}

func (p *Probe) m1000e() (bmcConnection interface{}, err error) {
func (p *Probe) m1000e(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {
resp, err := p.client.Get(fmt.Sprintf("https://%s/cgi-bin/webcgi/login", p.host))
if err != nil {
return bmcConnection, err
Expand All @@ -202,14 +204,14 @@ func (p *Probe) m1000e() (bmcConnection interface{}, err error) {
}

if resp.StatusCode == 200 && containsAnySubStr(payload, m1000eSysDesc) {
log.WithFields(log.Fields{"step": "connection", "host": p.host, "vendor": devices.Dell}).Debug("it's a m1000e chassis")
return m1000e.New(p.host, p.username, p.password)
log.V(1).Info("step", "connection", "host", p.host, "vendor", string(devices.Dell), "msg", "it's a m1000e chassis")
return m1000e.New(ctx, p.host, p.username, p.password, log)
}

return bmcConnection, errors.ErrDeviceNotMatched
}

func (p *Probe) supermicrox() (bmcConnection interface{}, err error) {
func (p *Probe) supermicrox(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {
resp, err := p.client.Get(fmt.Sprintf("https://%s/cgi/login.cgi", p.host))
if err != nil {
return bmcConnection, err
Expand All @@ -225,14 +227,14 @@ func (p *Probe) supermicrox() (bmcConnection interface{}, err error) {

// looking for ATEN in the response payload isn't the most ideal way, although it is unique to Supermicros
if resp.StatusCode == 200 && bytes.Contains(payload, []byte("ATEN International")) {
log.WithFields(log.Fields{"step": "connection", "host": p.host, "vendor": devices.Supermicro}).Debug("it's a supermicro")
return supermicrox.New(p.host, p.username, p.password)
log.V(1).Info("step", "connection", "host", p.host, "vendor", string(devices.Supermicro), "msg", "it's a supermicro")
return supermicrox.New(ctx, p.host, p.username, p.password, log)
}

return bmcConnection, errors.ErrDeviceNotMatched
}

func (p *Probe) quanta() (bmcConnection interface{}, err error) {
func (p *Probe) quanta(ctx context.Context, log logr.Logger) (bmcConnection interface{}, err error) {
resp, err := p.client.Get(fmt.Sprintf("https://%s/page/login.html", p.host))
if err != nil {
return bmcConnection, err
Expand All @@ -248,7 +250,7 @@ func (p *Probe) quanta() (bmcConnection interface{}, err error) {

// ensure the response we got included a png
if resp.StatusCode == 200 && bytes.Contains(payload, []byte("Quanta")) {
log.WithFields(log.Fields{"step": "ScanAndConnect", "host": p.host, "vendor": devices.Quanta}).Debug("it's a quanta")
log.V(1).Info("step", "ScanAndConnect", "host", p.host, "vendor", string(devices.Quanta), "msg", "it's a quanta")
return bmcConnection, errors.NewErrUnsupportedHardware("quanta hardware not supported")
}

Expand Down
109 changes: 109 additions & 0 deletions examples/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package main

import (
"os"

"github.com/bmc-toolbox/bmclib/devices"
"github.com/bmc-toolbox/bmclib/discover"
"github.com/bombsimon/logrusr"
"github.com/sirupsen/logrus"
)

// bmc lib takes in its opts a logger (https://github.com/go-logr/logr).
// If you do not define one, by default, it uses logrus (https://github.com/go-logr/logr)
// See the logr docs for more details, but the following implementations already exist:
// github.com/google/glog: glogr
// k8s.io/klog: klogr
// go.uber.org/zap: zapr
// log (the Go standard library logger): stdr
// github.com/sirupsen/logrus: logrusr
// github.com/wojas/genericr: genericr
func main() {

ip := "<bmc_ip>"
user := "user"
pass := "password"

logger := logrus.New()
logger.SetLevel(logrus.DebugLevel)
//logger.SetFormatter(&logrus.JSONFormatter{})

logger.Info("printing status with a user defined logger")
conn, err := withUserDefinedLogger(ip, user, pass, logger)
if err != nil {
logger.Fatal(err)
}
printStatus(conn, logger)

logger.Info("printing status with the default builtin logger")
os.Setenv("BMCLIB_LOG_LEVEL", "debug")
conn, err = withDefaultBuiltinLogger(ip, user, pass)
if err != nil {
logger.Fatal(err)
}
printStatus(conn, logger)
}

func withUserDefinedLogger(ip, user, pass string, logger *logrus.Logger) (interface{}, error) {
myLog := logrusr.NewLogger(logger)
opts := func(o *discover.Options) {
o.Logger = myLog
}

return discover.ScanAndConnect(ip, user, pass, opts)
}

func withDefaultBuiltinLogger(ip, user, pass string) (interface{}, error) {
return discover.ScanAndConnect(ip, user, pass)
}

func printStatus(connection interface{}, logger *logrus.Logger) {
switch connection.(type) {
case devices.Bmc:
conn := connection.(devices.Bmc)
defer conn.Close()

sr, err := conn.Serial()
if err != nil {
logger.Fatal(err)
}
logger.WithFields(logrus.Fields{"serial": sr}).Info("serial")

md, err := conn.Model()
if err != nil {
logger.Fatal(err)
}
logger.WithFields(logrus.Fields{"model": md}).Info("model")

mm, err := conn.Memory()
if err != nil {
logger.Fatal(err)
}
logger.WithFields(logrus.Fields{"memory": mm}).Info("memory")

st, err := conn.Status()
if err != nil {
logger.Fatal(err)
}
logger.WithFields(logrus.Fields{"status": st}).Info("status")

hw := conn.HardwareType()
logger.WithFields(logrus.Fields{"hwType": hw}).Info("hwType")

state, err := conn.PowerState()
if err != nil {
logger.Fatal(err)
}
logger.WithFields(logrus.Fields{"state": state}).Info("state")

case devices.Cmc:
cmc := connection.(devices.Cmc)
sts, err := cmc.Status()
if err != nil {
logger.Fatal(err)
}
logger.WithFields(logrus.Fields{"status": sts}).Info("status")
default:
logger.Fatal("Unknown device")
}
}
Loading

0 comments on commit 13026a9

Please sign in to comment.