Skip to content

Commit

Permalink
Merge pull request #520 from newrelic/chore/report-status
Browse files Browse the repository at this point in the history
chore(install): add nerdstorageExecutionStatusReporter
  • Loading branch information
ctrombley authored Dec 17, 2020
2 parents 7102c75 + 72f1fef commit dc32052
Show file tree
Hide file tree
Showing 20 changed files with 825 additions and 81 deletions.
2 changes: 2 additions & 0 deletions internal/install/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ var Command = &cobra.Command{
rf := newServiceRecipeFetcher(&nrClient.NerdGraph)
pf := newRegexProcessFilterer(rf)
ff := newRecipeFileFetcher()
er := newNerdStorageExecutionStatusReporter(&nrClient.NerdStorage)

i := newRecipeInstaller(ic,
newPSUtilDiscoverer(pf),
Expand All @@ -55,6 +56,7 @@ var Command = &cobra.Command{
newGoTaskRecipeExecutor(),
newPollingRecipeValidator(&nrClient.Nrdb),
ff,
er,
)

// Run the install.
Expand Down
89 changes: 89 additions & 0 deletions internal/install/execution_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package install

import (
"time"

"github.com/google/uuid"
)

type executionStatusRollup struct {
Complete bool `json:"complete"`
DocumentID string
EntityGuids []string `json:"entityGuids"`
Statuses []executionStatus `json:"recipes"`
Timestamp int64 `json:"timestamp"`
}

type executionStatus struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Status executionStatusType `json:"status"`
Errors []executionStatusRecipeError `json:"errors"`
}

type executionStatusType string

var executionStatusTypes = struct {
AVAILABLE executionStatusType
FAILED executionStatusType
INSTALLED executionStatusType
SKIPPED executionStatusType
}{
AVAILABLE: "AVAILABLE",
FAILED: "FAILED",
INSTALLED: "INSTALLED",
SKIPPED: "SKIPPED",
}

type executionStatusRecipeError struct {
Message string `json:"message"`
Details string `json:"details"`
}

func newExecutionStatusRollup() executionStatusRollup {
s := executionStatusRollup{
DocumentID: uuid.New().String(),
Timestamp: getTimestamp(),
}

return s
}

func getTimestamp() int64 {
return time.Now().Unix()
}

func (s *executionStatusRollup) withAvailableRecipes(recipes []recipe) {
for _, r := range recipes {
e := recipeStatusEvent{recipe: r}
s.withRecipeEvent(e, executionStatusTypes.AVAILABLE)
}
}

func (s *executionStatusRollup) withRecipeEvent(e recipeStatusEvent, rs executionStatusType) {
found := s.getExecutionStatusRecipe(e.recipe)

if found != nil {
found.Status = rs
} else {
e := &executionStatus{
Name: e.recipe.Name,
DisplayName: e.recipe.DisplayName,
Status: rs,
}
s.Statuses = append(s.Statuses, *e)
}

s.Timestamp = getTimestamp()
}

func (s *executionStatusRollup) getExecutionStatusRecipe(r recipe) *executionStatus {
var found *executionStatus
for i, recipe := range s.Statuses {
if recipe.Name == r.Name {
found = &s.Statuses[i]
}
}

return found
}
13 changes: 13 additions & 0 deletions internal/install/execution_status_reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package install

type executionStatusReporter interface {
reportRecipeFailed(event recipeStatusEvent) error
reportRecipeInstalled(event recipeStatusEvent) error
reportRecipesAvailable(recipes []recipe) error
}

type recipeStatusEvent struct {
recipe recipe
msg string
entityGUID string
}
67 changes: 67 additions & 0 deletions internal/install/execution_status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//+ build unit
package install

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestNewExecutionStatus(t *testing.T) {
s := newExecutionStatusRollup()
require.NotEmpty(t, s.Timestamp)
require.NotEmpty(t, s.DocumentID)
}

func TestExecutionStatusWithAvailableRecipes_Basic(t *testing.T) {
s := newExecutionStatusRollup()
r := []recipe{{
Name: "testRecipe1",
}, {
Name: "testRecipe2",
}}

s.withAvailableRecipes(r)

require.NotEmpty(t, s.Statuses)
require.Equal(t, len(r), len(s.Statuses))
for _, recipeStatus := range s.Statuses {
require.Equal(t, executionStatusTypes.AVAILABLE, recipeStatus.Status)
}
}

func TestExecutionStatusWithRecipeEvent_Basic(t *testing.T) {
s := newExecutionStatusRollup()
r := recipe{Name: "testRecipe"}
e := recipeStatusEvent{recipe: r}

s.Timestamp = 0
s.withRecipeEvent(e, executionStatusTypes.INSTALLED)

require.NotEmpty(t, s.Statuses)
require.Equal(t, 1, len(s.Statuses))
require.Equal(t, executionStatusTypes.INSTALLED, s.Statuses[0].Status)
require.NotEmpty(t, s.Timestamp)
}

func TestExecutionStatusWithRecipeEvent_RecipeExists(t *testing.T) {
s := newExecutionStatusRollup()
r := recipe{Name: "testRecipe"}
e := recipeStatusEvent{recipe: r}

s.Timestamp = 0
s.withRecipeEvent(e, executionStatusTypes.AVAILABLE)

require.NotEmpty(t, s.Statuses)
require.Equal(t, 1, len(s.Statuses))
require.Equal(t, executionStatusTypes.AVAILABLE, s.Statuses[0].Status)
require.NotEmpty(t, s.Timestamp)

s.Timestamp = 0
s.withRecipeEvent(e, executionStatusTypes.INSTALLED)

require.NotEmpty(t, s.Statuses)
require.Equal(t, 1, len(s.Statuses))
require.Equal(t, executionStatusTypes.INSTALLED, s.Statuses[0].Status)
require.NotEmpty(t, s.Timestamp)
}
29 changes: 29 additions & 0 deletions internal/install/mock_execution_status_reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package install

type mockExecutionStatusReporter struct {
reportRecipesAvailableErr error
reportRecipeFailedErr error
reportRecipeInstalledErr error
reportRecipesAvailableCallCount int
reportRecipeFailedCallCount int
reportRecipeInstalledCallCount int
}

func newMockExecutionStatusReporter() *mockExecutionStatusReporter {
return &mockExecutionStatusReporter{}
}

func (r *mockExecutionStatusReporter) reportRecipeFailed(event recipeStatusEvent) error {
r.reportRecipeFailedCallCount++
return r.reportRecipeFailedErr
}

func (r *mockExecutionStatusReporter) reportRecipeInstalled(event recipeStatusEvent) error {
r.reportRecipeInstalledCallCount++
return r.reportRecipeInstalledErr
}

func (r *mockExecutionStatusReporter) reportRecipesAvailable(recipes []recipe) error {
r.reportRecipesAvailableCallCount++
return r.reportRecipesAvailableErr
}
31 changes: 31 additions & 0 deletions internal/install/mock_nerdstorage_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package install

import "github.com/newrelic/newrelic-client-go/pkg/nerdstorage"

// nolint:unused,deadcode
type mockNerdstorageClient struct {
respBody interface{}
userScopeError error
entityScopeError error
writeDocumentWithUserScopeCallCount int
writeDocumentWithEntityScopeCallCount int
}

// nolint:unused,deadcode
func newMockNerdstorageClient() *mockNerdstorageClient {
return &mockNerdstorageClient{
respBody: struct{}{},
userScopeError: nil,
entityScopeError: nil,
}
}

func (c *mockNerdstorageClient) WriteDocumentWithUserScope(nerdstorage.WriteDocumentInput) (interface{}, error) {
c.writeDocumentWithUserScopeCallCount++
return c.respBody, c.userScopeError
}

func (c *mockNerdstorageClient) WriteDocumentWithEntityScope(string, nerdstorage.WriteDocumentInput) (interface{}, error) {
c.writeDocumentWithEntityScopeCallCount++
return c.respBody, c.entityScopeError
}
54 changes: 35 additions & 19 deletions internal/install/mock_recipe_fetcher.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,58 @@
package install

import "context"
import (
"context"
)

type mockRecipeFetcher struct {
fetchRecipeFunc func(*discoveryManifest, string) (*recipe, error)
fetchRecipesFunc func() ([]recipe, error)
fetchRecommendationsFunc func(*discoveryManifest) ([]recipe, error)
fetchRecipeErr error
fetchRecipesErr error
fetchRecommendationsErr error
fetchRecipeCallCount int
fetchRecipesCallCount int
fetchRecommendationsCallCount int
fetchRecipeVals []recipe
fetchRecipeVal *recipe
fetchRecipesVal []recipe
fetchRecommendationsVal []recipe
}

func newMockRecipeFetcher() *mockRecipeFetcher {
f := mockRecipeFetcher{}
f.fetchRecipeFunc = defaultFetchRecipeFunc
f.fetchRecipesFunc = defaultFetchRecipesFunc
f.fetchRecommendationsFunc = defaultFetchRecommendationsFunc

f.fetchRecipesVal = []recipe{}
f.fetchRecommendationsVal = []recipe{}
return &f
}

func (f *mockRecipeFetcher) fetchRecipe(ctx context.Context, manifest *discoveryManifest, friendlyName string) (*recipe, error) {
return f.fetchRecipeFunc(manifest, friendlyName)
f.fetchRecipeCallCount++

if len(f.fetchRecipeVals) > 0 {
i := minOf(f.fetchRecipeCallCount, len(f.fetchRecipeVals)) - 1
return &f.fetchRecipeVals[i], f.fetchRecipesErr
}

return f.fetchRecipeVal, f.fetchRecipeErr
}

func (f *mockRecipeFetcher) fetchRecipes(ctx context.Context) ([]recipe, error) {
return f.fetchRecipesFunc()
f.fetchRecipesCallCount++
return f.fetchRecipesVal, f.fetchRecipesErr
}

func (f *mockRecipeFetcher) fetchRecommendations(ctx context.Context, manifest *discoveryManifest) ([]recipe, error) {
return f.fetchRecommendationsFunc(manifest)
f.fetchRecommendationsCallCount++
return f.fetchRecommendationsVal, f.fetchRecommendationsErr
}

func defaultFetchRecipeFunc(manifest *discoveryManifest, friendlyName string) (*recipe, error) {
return &recipe{}, nil
}
func minOf(vars ...int) int {
min := vars[0]

func defaultFetchRecommendationsFunc(manifest *discoveryManifest) ([]recipe, error) {
return []recipe{}, nil
}
for _, i := range vars {
if min > i {
min = i
}
}

func defaultFetchRecipesFunc() ([]recipe, error) {
return []recipe{}, nil
return min
}
14 changes: 8 additions & 6 deletions internal/install/mock_recipe_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ package install
import "context"

type mockRecipeValidator struct {
result func(discoveryManifest, recipe) (bool, error)
validateErr error
validateCallCount int
validateVal bool
validateEntityGUIDVal string
}

func newMockRecipeValidator() *mockRecipeValidator {
return &mockRecipeValidator{
result: func(discoveryManifest, recipe) (bool, error) { return false, nil },
}
return &mockRecipeValidator{}
}

func (m *mockRecipeValidator) validate(ctx context.Context, dm discoveryManifest, r recipe) (bool, error) {
return m.result(dm, r)
func (m *mockRecipeValidator) validate(ctx context.Context, dm discoveryManifest, r recipe) (bool, string, error) {
m.validateCallCount++
return m.validateVal, m.validateEntityGUIDVal, m.validateErr
}
10 changes: 10 additions & 0 deletions internal/install/nerdstorage_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package install

import (
"github.com/newrelic/newrelic-client-go/pkg/nerdstorage"
)

type nerdstorageClient interface {
WriteDocumentWithUserScope(nerdstorage.WriteDocumentInput) (interface{}, error)
WriteDocumentWithEntityScope(string, nerdstorage.WriteDocumentInput) (interface{}, error)
}
Loading

0 comments on commit dc32052

Please sign in to comment.