Skip to content

Commit

Permalink
checker: rewrote checker.Run to return a Result directly instead of s…
Browse files Browse the repository at this point in the history
…tring, error

Will allow control of CRITICAL / WARNING state directly from plugins
  • Loading branch information
rbeuque74 committed May 9, 2018
1 parent f9ead7f commit a49c273
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 113 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ script:
after_script:
- go get -v github.com/alecthomas/gometalinter/...
- gometalinter --install
- gometalinter --enable-all
- gometalinter --enable-all --disable=lll

after_success:
- rm -v jagozzi
Expand Down
9 changes: 6 additions & 3 deletions consumers/nsca/nsca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package nsca
import (
"context"
"encoding/json"
"errors"
"fmt"
"testing"
"time"
Expand All @@ -25,9 +24,13 @@ func (fc fakeChecker) ServiceName() string {
return "fake-service-name"
}

func (fc fakeChecker) Run(ctx context.Context) (string, error) {
func (fc fakeChecker) Run(ctx context.Context) plugins.Result {
fc.t.Fatal("fake checker should not run")
return "", errors.New("fake checker should not run")
return plugins.Result{
Status: plugins.STATE_CRITICAL,
Message: "fake checker should never run",
Checker: fc,
}
}

func TestConsumerSendMessage(t *testing.T) {
Expand Down
27 changes: 8 additions & 19 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,29 +125,18 @@ func (yag Jagozzi) runMainLoop(ctx context.Context, wg *sync.WaitGroup) {
func (yag Jagozzi) runChecker(ctx context.Context, checker plugins.Checker, wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()
result, err := checker.Run(ctx)
if err == nil {
log.Infof("checker: result was %q", result)
yag.SendConsumers(ctx, plugins.Result{
Status: plugins.STATE_OK,
Message: result,
Checker: checker,
})
return
}
result := checker.Run(ctx)

if ctx.Err() != nil && ctx.Err() == context.Canceled {
log.Println("jagozzi: context cancelled: ", err)
log.Infof("jagozzi: context cancelled while running checker: %s", checker.Name())
return
} else if ctx.Err() != nil && ctx.Err() == context.DeadlineExceeded {
log.Println("jagozzi: context timed out: ", err)
return
log.Errorf("jagozzi: context timed out while running checker: %s", checker.Name())
ctxConsumer, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
defer cancel()
ctx = ctxConsumer
}

log.Errorf("checker: %s", err)
yag.SendConsumers(ctx, plugins.Result{
Status: plugins.STATE_CRITICAL,
Message: err.Error(),
Checker: checker,
})
log.Debugf("checker: result was %q", result.Message)
yag.SendConsumers(ctx, result)
}
24 changes: 15 additions & 9 deletions plugins/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type CommandChecker struct {
}

// Name returns the name of the checker
func (c *CommandChecker) Name() string {
func (c CommandChecker) Name() string {
return pluginName
}

Expand All @@ -50,7 +50,7 @@ func (res result) Error() error {
}

// Run is performing the checker protocol
func (c *CommandChecker) Run(ctx context.Context) (string, error) {
func (c *CommandChecker) Run(ctx context.Context) plugins.Result {
cmd := exec.Command(c.command, c.args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
Expand All @@ -65,8 +65,7 @@ func (c *CommandChecker) Run(ctx context.Context) (string, error) {
}

if err := cmd.Start(); err != nil {
log.Warn("command: can't start")
return "KO", err
return plugins.ResultFromError(c, err, "")
}
done := make(chan error)
go func() { done <- cmd.Wait() }()
Expand All @@ -76,22 +75,29 @@ func (c *CommandChecker) Run(ctx context.Context) (string, error) {
model.Stdout = stdout.String()

if err := cmd.Process.Kill(); err != nil {
return "KO", fmt.Errorf("command: context expired, kill pid %q failed: %s", cmd.Process.Pid, err)
return plugins.ResultFromError(c, err, fmt.Sprintf("command: context expired, kill pid %q failed", cmd.Process.Pid))
}

model.Err = ctx.Err()
return "KO", fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrTimeout, model))
err := fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrTimeout, model))
return plugins.ResultFromError(c, err, "")
case err := <-done:
model.Stderr = stderr.String()
model.Stdout = stdout.String()

if typedErr, ok := err.(*exec.ExitError); ok {
model.ExitCode = typedErr.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
model.Err = fmt.Errorf("%s: %s", typedErr, model.Stderr)
return "KO", fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrExitCode, model))
err = fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrExitCode, model))
return plugins.ResultFromError(c, err, "")
} else if err != nil {
return "KO", fmt.Errorf("%s: %s", err, model.Stderr)
return plugins.ResultFromError(c, fmt.Errorf("%s: %s", err, model.Stderr), "")
} else {
return model.Stdout, nil
return plugins.Result{
Status: plugins.STATE_OK,
Message: model.Stdout,
Checker: c,
}
}
}
}
Expand Down
50 changes: 50 additions & 0 deletions plugins/command/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package command

import (
"context"
"testing"
"time"

"github.com/rbeuque74/jagozzi/plugins"
"github.com/stretchr/testify/assert"
)

func TestCommand(t *testing.T) {
// creating Command checker
cfg := map[string]interface{}{
"command": "/bin/echo HelloWorld",
"name": "test-1",
}
checker, err := NewCommandChecker(cfg, nil)
assert.Nilf(t, err, "command checker instantiation failed: %q", err)

ctxRun, cancelFunc1 := context.WithTimeout(context.Background(), time.Second)
defer cancelFunc1()
result := checker.Run(ctxRun)
assert.Equal(t, plugins.STATE_OK, result.Status)
assert.Containsf(t, result.Message, "HelloWorld\n", "command bad message: %q", result.Message)

// bad exit code
cfg["command"] = "/bin/false"

checker, err = NewCommandChecker(cfg, nil)
assert.Nilf(t, err, "command checker instantiation failed: %q", err)

ctxRun, cancelFunc1 = context.WithTimeout(context.Background(), time.Second)
defer cancelFunc1()
result = checker.Run(ctxRun)
assert.Equal(t, plugins.STATE_CRITICAL, result.Status)
assert.Equal(t, result.Message, "command /bin/false exited with status code 1")

// timeout
cfg["command"] = "/bin/sleep 2"

checker, err = NewCommandChecker(cfg, nil)
assert.Nilf(t, err, "command checker instantiation failed: %q", err)

ctxRun, cancelFunc1 = context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancelFunc1()
result = checker.Run(ctxRun)
assert.Equal(t, plugins.STATE_CRITICAL, result.Status)
assert.Equal(t, result.Message, "command /bin/sleep 2 took too long to execute")
}
2 changes: 1 addition & 1 deletion plugins/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type WithServiceName interface {
type Checker interface {
WithServiceName
Name() string
Run(context.Context) (string, error)
Run(context.Context) Result
}

// CheckerFactory is the function interface to creates a checker instance
Expand Down
41 changes: 31 additions & 10 deletions plugins/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type HTTPChecker struct {
}

// Name returns the name of the checker
func (c *HTTPChecker) Name() string {
func (c HTTPChecker) Name() string {
return pluginName
}

Expand All @@ -50,7 +50,7 @@ func (res result) Error() error {
}

// Run is performing the checker protocol
func (c *HTTPChecker) Run(ctx context.Context) (string, error) {
func (c *HTTPChecker) Run(ctx context.Context) plugins.Result {
httpCtx, cancel := context.WithTimeout(ctx, c.cfg.Timeout)
defer cancel()

Expand All @@ -63,7 +63,8 @@ func (c *HTTPChecker) Run(ctx context.Context) (string, error) {
req, err := http.NewRequest(c.cfg.Method, c.cfg.URL, nil)
if err != nil {
model.Err = err
return "", fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrNewHTTPRequest, model))
err = fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrNewHTTPRequest, model))
return plugins.ResultFromError(c, err, "")
}
req = req.WithContext(httpCtx)

Expand All @@ -73,7 +74,8 @@ func (c *HTTPChecker) Run(ctx context.Context) (string, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
model.Err = err
return "", fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrRequest, model))
err = fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrRequest, model))
return plugins.ResultFromError(c, err, "")
}
defer resp.Body.Close()

Expand All @@ -94,17 +96,36 @@ func (c *HTTPChecker) Run(ctx context.Context) (string, error) {

if resp.StatusCode != int(c.cfg.Code) {
model.Err = fmt.Errorf("invalid status code: %d instead of %d", resp.StatusCode, c.cfg.Code)
return "", fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrStatusCode, model))

return plugins.Result{
Checker: c,
Message: plugins.RenderError(c.cfg.templates.ErrStatusCode, model),
Status: plugins.STATE_CRITICAL,
}
}

if elapsedTime > c.cfg.Critical {
model.Err = fmt.Errorf("critical timeout: request took %s instead of %s", elapsedTime, c.cfg.Critical.Round(time.Millisecond))
return "", fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrTimeoutCritical, model))
model.Err = fmt.Errorf("critical timeout: request took %s instead of %s (%s)", elapsedTime, c.cfg.Critical.Round(time.Millisecond), resp.Status)

return plugins.Result{
Checker: c,
Message: plugins.RenderError(c.cfg.templates.ErrTimeoutCritical, model),
Status: plugins.STATE_CRITICAL,
}
} else if elapsedTime > c.cfg.Warning {
model.Err = fmt.Errorf("timeout: request took %s instead of %s", elapsedTime, c.cfg.Warning.Round(time.Millisecond))
return "", fmt.Errorf(plugins.RenderError(c.cfg.templates.ErrTimeoutWarning, model))
model.Err = fmt.Errorf("timeout: request took %s instead of %s (%s)", elapsedTime, c.cfg.Warning.Round(time.Millisecond), resp.Status)

return plugins.Result{
Checker: c,
Message: plugins.RenderError(c.cfg.templates.ErrTimeoutWarning, model),
Status: plugins.STATE_WARNING,
}
}
return plugins.Result{
Checker: c,
Message: fmt.Sprintf("%s - %s elapsed", resp.Status, elapsedTime),
Status: plugins.STATE_OK,
}
return "OK", nil
}

// NewHTTPChecker create a HTTP checker
Expand Down
Loading

0 comments on commit a49c273

Please sign in to comment.