Skip to content
This repository has been archived by the owner on Nov 8, 2019. It is now read-only.

Commit

Permalink
Merge pull request #18 from ingrammicro/feature/14-subcommand-to-run-…
Browse files Browse the repository at this point in the history
…a-bootstrap-script-and-gradually-report-its-output

Implement subcommand Continuous Report Run (issue #14)
  • Loading branch information
Pablo Baños López authored Jan 29, 2018
2 parents 52fc16b + 2057e3f commit d2f1919
Show file tree
Hide file tree
Showing 11 changed files with 498 additions and 62 deletions.
16 changes: 16 additions & 0 deletions api/polling/polling_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,19 @@ func (p *PollingService) UpdateCommand(pollingCommandVector *map[string]interfac

return command, status, nil
}

// ReportBootstrapLog reports a command result
func (p *PollingService) ReportBootstrapLog(PollingContinuousReportVector *map[string]interface{}) (command *types.PollingContinuousReport, status int, err error) {
log.Debug("ReportBootstrapLog")

data, status, err := p.concertoService.Post("/command_polling/bootstrap_logs", PollingContinuousReportVector)
if err != nil {
return nil, status, err
}

if err = json.Unmarshal(data, &command); err != nil {
return nil, status, err
}

return command, status, nil
}
113 changes: 113 additions & 0 deletions api/polling/polling_api_mocked.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,118 @@ func UpdateCommandFailJSONMocked(t *testing.T, commandIn *types.PollingCommand)
assert.Nil(commandOut, "Expecting nil output")
assert.Contains(err.Error(), "invalid character", "Error message should include the string 'invalid character'")

return commandOut
}


// ReportBootstrapLogMocked test mocked function
func ReportBootstrapLogMocked(t *testing.T, commandIn *types.PollingContinuousReport) *types.PollingContinuousReport {

assert := assert.New(t)

// wire up
cs := &utils.MockConcertoService{}
ds, err := NewPollingService(cs)
assert.Nil(err, "Couldn't load polling service")
assert.NotNil(ds, "Polling service not instanced")

// to json
dOut, err := json.Marshal(commandIn)
assert.Nil(err, "ReportBootstrapLog test data corrupted")

// call service
payload := make(map[string]interface{})
cs.On("Post", fmt.Sprintf("/command_polling/bootstrap_logs"), &payload).Return(dOut, 201, nil)
commandOut, status, err := ds.ReportBootstrapLog(&payload)

assert.Nil(err, "Error posting report command")
assert.Equal(status, 201, "ReportBootstrapLog returned invalid response")
assert.Equal(commandOut.Stdout, "Bootstrap log created", "ReportBootstrapLog returned unexpected message")

return commandOut
}

// ReportBootstrapLogFailErrMocked test mocked function
func ReportBootstrapLogFailErrMocked(t *testing.T, commandIn *types.PollingContinuousReport) *types.PollingContinuousReport {

assert := assert.New(t)

// wire up
cs := &utils.MockConcertoService{}
ds, err := NewPollingService(cs)
assert.Nil(err, "Couldn't load polling service")
assert.NotNil(ds, "Polling service not instanced")

// to json
dIn, err := json.Marshal(commandIn)
assert.Nil(err, "ReportBootstrapLog test data corrupted")

dIn = nil

// call service
payload := make(map[string]interface{})
cs.On("Post", fmt.Sprintf("/command_polling/bootstrap_logs"), &payload).Return(dIn, 400, fmt.Errorf("Mocked error"))
commandOut, _, err := ds.ReportBootstrapLog(&payload)

assert.NotNil(err, "We are expecting an error")
assert.Nil(commandOut, "Expecting nil output")
assert.Equal(err.Error(), "Mocked error", "Error should be 'Mocked error'")

return commandOut
}

// ReportBootstrapLogFailStatusMocked test mocked function
func ReportBootstrapLogFailStatusMocked(t *testing.T, commandIn *types.PollingContinuousReport) *types.PollingContinuousReport {

assert := assert.New(t)

// wire up
cs := &utils.MockConcertoService{}
ds, err := NewPollingService(cs)
assert.Nil(err, "Couldn't load polling service")
assert.NotNil(ds, "Polling service not instanced")

// to json
dIn, err := json.Marshal(commandIn)
assert.Nil(err, "ReportBootstrapLog test data corrupted")

dIn = nil

// call service
payload := make(map[string]interface{})
cs.On("Post", fmt.Sprintf("/command_polling/bootstrap_logs"), &payload).Return(dIn, 499, fmt.Errorf("Error 499 Mocked error"))
commandOut, status, err := ds.ReportBootstrapLog(&payload)

assert.Equal(status, 499, "ReportBootstrapLog returned an unexpected status code")
assert.NotNil(err, "We are expecting a status code error")
assert.Nil(commandOut, "Expecting nil output")
assert.Contains(err.Error(), "499", "Error should contain http code 499")

return commandOut
}

// ReportBootstrapLogFailJSONMocked test mocked function
func ReportBootstrapLogFailJSONMocked(t *testing.T, commandIn *types.PollingContinuousReport) *types.PollingContinuousReport {

assert := assert.New(t)

// wire up
cs := &utils.MockConcertoService{}
ds, err := NewPollingService(cs)
assert.Nil(err, "Couldn't load polling service")
assert.NotNil(ds, "Polling service not instanced")

// wrong json
dIn := []byte{10, 20, 30}

// call service
payload := make(map[string]interface{})
cs.On("Post", fmt.Sprintf("/command_polling/bootstrap_logs"), &payload).Return(dIn, 201, nil)
commandOut, _, err := ds.ReportBootstrapLog(&payload)

assert.NotNil(err, "We are expecting a marshalling error")
assert.Nil(commandOut, "Expecting nil output")
assert.Contains(err.Error(), "invalid character", "Error message should include the string 'invalid character'")

return commandOut
}
9 changes: 9 additions & 0 deletions api/polling/polling_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ func TestUpdateCommand(t *testing.T) {
UpdateCommandFailStatusMocked(t, commandIn)
UpdateCommandFailJSONMocked(t, commandIn)
}


func TestReportBootstrapLog(t *testing.T) {
commandIn := testdata.GetPollingContinuousReportData()
ReportBootstrapLogMocked(t, commandIn)
ReportBootstrapLogFailErrMocked(t, commandIn)
ReportBootstrapLogFailStatusMocked(t, commandIn)
ReportBootstrapLogFailJSONMocked(t, commandIn)
}
5 changes: 5 additions & 0 deletions api/types/polling_continuous_report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package types

type PollingContinuousReport struct {
Stdout string `json:"stdout" header:"STDOUT"`
}
95 changes: 95 additions & 0 deletions cmdpolling/continuousreport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmdpolling

import (
"errors"
"fmt"
"os"
"time"

log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/ingrammicro/concerto/cmd"
"github.com/ingrammicro/concerto/utils"
"github.com/ingrammicro/concerto/utils/format"
)

const (
RetriesNumber = 5
RetriesFactor = 3
DefaultThresholdTime = 10
)

func cmdContinuousReportRun(c *cli.Context) error {
log.Debug("cmdContinuousReportRun")

formatter := format.GetFormatter()
pollingSvc := cmd.WireUpPolling(c)

// cli command argument
var cmdArg string
if c.Args().Present() {
cmdArg = c.Args().First()
} else {
formatter.PrintFatal("argument missing", errors.New("a script or command is required"))
}

// cli command threshold flag
thresholdTime := c.Int("time")
if !(thresholdTime > 0) {
thresholdTime = DefaultThresholdTime
}
log.Debug("Time threshold:", thresholdTime)

// Custom method for chunks processing
fn := func(chunk string) error {
log.Debug("sendChunks")
err := retry(RetriesNumber, time.Second, func() error {
log.Debug("Sending: ", chunk)

commandIn := map[string]interface{}{
"stdout": chunk,
}

_, statusCode, err := pollingSvc.ReportBootstrapLog(&commandIn)
switch {
// 0<100 error cases??
case statusCode == 0:
return fmt.Errorf("communication error %v %v", statusCode, err)
case statusCode >= 500:
return fmt.Errorf("server error %v %v", statusCode, err)
case statusCode >= 400:
return fmt.Errorf("client error %v %v", statusCode, err)
default:
return nil
}
})

if err != nil {
return fmt.Errorf("cannot send the chunk data, %v", err)
}
return nil
}

exitCode, err := utils.RunContinuousCmd(fn, cmdArg, thresholdTime)
if err != nil {
formatter.PrintFatal("cannot process continuous report command", err)
}

log.Info("completed: ", exitCode)
os.Exit(exitCode)
return nil
}

func retry(attempts int, sleep time.Duration, fn func() error) error {
log.Debug("retry")

if err := fn(); err != nil {
if attempts--; attempts > 0 {
log.Debug("Waiting to retry: ", sleep)
time.Sleep(sleep)
return retry(attempts, RetriesFactor*sleep, fn)
}
return err
}
return nil
}
Loading

0 comments on commit d2f1919

Please sign in to comment.