diff --git a/go.mod b/go.mod index 793c5d13..9bf6df2d 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/pkg/term v1.1.0 github.com/shirou/gopsutil/v3 v3.21.3 github.com/stretchr/testify v1.6.1 + github.com/superfly/fly-checks v0.0.0-20230510154016-d189351293f2 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index 6a6ad732..183b39f1 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/superfly/fly-checks v0.0.0-20230510154016-d189351293f2 h1:mZNiJSrmbQA/3+Vy8GLL/Q9qdHxPzjcxKv+E14GZLFs= +github.com/superfly/fly-checks v0.0.0-20230510154016-d189351293f2/go.mod h1:BbqpB4y6Z/cijQqKWJ3i8LMsAoC29gzX6vsSD3Qq7uw= github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= diff --git a/pkg/check/check.go b/pkg/check/check.go deleted file mode 100644 index 0ffa3573..00000000 --- a/pkg/check/check.go +++ /dev/null @@ -1,61 +0,0 @@ -package check - -import ( - "fmt" - "time" -) - -type CheckFunction func() (string, error) -type Check struct { - Name string - CheckFunc CheckFunction - startTime time.Time - endTime time.Time - message string - err error -} - -func (c *Check) Process() { - c.startTime = time.Now() - c.message, c.err = c.CheckFunc() - c.endTime = time.Now() -} - -func (c *Check) Error() string { - return c.err.Error() -} - -func (c *Check) Passed() bool { - if c.startTime.IsZero() || c.endTime.IsZero() { - return false - } - return c.err == nil -} - -func (c *Check) ExecutionTime() time.Duration { - if !c.endTime.IsZero() { - return RoundDuration(c.endTime.Sub(c.startTime), 2) - } - return RoundDuration(time.Now().Sub(c.startTime), 2) -} - -func (c *Check) Result() string { - if c.startTime.IsZero() { - return fmt.Sprintf("[-] %s: %s", c.Name, "Not processed") - } - if !c.startTime.IsZero() && c.endTime.IsZero() { - return fmt.Sprintf("[✗] %s: %s (%s)", c.Name, "Timed out", c.ExecutionTime()) - } - if c.Passed() { - return fmt.Sprintf("[✓] %s: %s (%s)", c.Name, c.message, c.ExecutionTime()) - } else { - return fmt.Sprintf("[✗] %s: %s (%s)", c.Name, c.err.Error(), c.ExecutionTime()) - } -} - -func (c *Check) RawResult() string { - if c.Passed() { - return c.message - } - return c.err.Error() -} diff --git a/pkg/check/check_suite.go b/pkg/check/check_suite.go deleted file mode 100644 index 4468dbcc..00000000 --- a/pkg/check/check_suite.go +++ /dev/null @@ -1,103 +0,0 @@ -package check - -import ( - "context" - "errors" - "fmt" - "strings" - "time" -) - -type OnCompletionHook func() -type CheckSuite struct { - Name string - Checks []*Check - OnCompletion OnCompletionHook - ErrOnSetup error - executionTime time.Duration - processed bool - clean bool -} - -func NewCheckSuite(name string) *CheckSuite { - return &CheckSuite{Name: name} -} - -func (h *CheckSuite) Process(parentCtx context.Context) { - ctx, cancel := context.WithCancel(parentCtx) - start := time.Now() - for _, check := range h.Checks { - check.Process() - } - h.executionTime = RoundDuration(time.Since(start), 2) - h.processed = true - h.runOnCompletion() - cancel() - - select { - case <-ctx.Done(): - // Handle timeout - if errors.Is(ctx.Err(), context.DeadlineExceeded) { - h.executionTime = RoundDuration(time.Since(start), 2) - h.processed = true - h.runOnCompletion() - } - } -} - -func (h *CheckSuite) runOnCompletion() { - if h.clean { - return - } - if h.OnCompletion != nil { - h.OnCompletion() - } - h.clean = true -} - -func (h *CheckSuite) AddCheck(name string, checkFunc CheckFunction) *Check { - check := &Check{Name: name, CheckFunc: checkFunc} - h.Checks = append(h.Checks, check) - return check -} - -func (h *CheckSuite) Passed() bool { - for _, check := range h.Checks { - if !check.Passed() { - return false - } - } - return true -} - -func (h *CheckSuite) Result() string { - checkStr := []string{} - for _, check := range h.Checks { - checkStr = append(checkStr, check.Result()) - } - return strings.Join(checkStr, "\n") -} - -func (h *CheckSuite) RawResult() string { - checkStr := []string{} - for _, check := range h.Checks { - checkStr = append(checkStr, check.RawResult()) - } - return strings.Join(checkStr, "\n") -} - -// Print will send output straight to stdout. -func (h *CheckSuite) Print() { - if h.processed { - for _, check := range h.Checks { - fmt.Println(check.Result()) - } - fmt.Printf("Total execution time of %q checks: %s\n", h.Name, h.executionTime) - } else { - if len(h.Checks) > 0 { - fmt.Printf("%q hasn't been processed. %d check(s) pending evaluation.\n", h.Name, len(h.Checks)) - } else { - fmt.Printf("%q has no checks to evaluate.\n", h.Name) - } - } -} diff --git a/pkg/check/check_suite_test.go b/pkg/check/check_suite_test.go deleted file mode 100644 index ba0721f0..00000000 --- a/pkg/check/check_suite_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package check - -import ( - "context" - "errors" - "fmt" - "sort" - "strings" - "testing" - "time" -) - -func TestPassingCheckSuite(t *testing.T) { - suite := NewCheckSuite("passingChecks") - suite.AddCheck("test_one", func() (string, error) { - return "pass", nil - }) - suite.AddCheck("test_two", func() (string, error) { - return "pass", nil - }) - suite.AddCheck("test_three", func() (string, error) { - return "pass", nil - }) - - if len(suite.Checks) != 3 { - t.Fatalf("expected suite to contain %d checks, but had %d instead.", 3, len(suite.Checks)) - } - - suite.Process(context.TODO()) - - if !suite.Passed() { - t.Fatalf("expected %s to pass, but didn't", suite.Name) - } -} - -func TestFailingCheckSuite(t *testing.T) { - suite := NewCheckSuite("passingChecks") - - suite.AddCheck("test_one", func() (string, error) { - return "pass", nil - }) - suite.AddCheck("test_two", func() (string, error) { - return "pass", nil - }) - suite.AddCheck("test_three", func() (string, error) { - return "", fmt.Errorf("I failed") - }) - - if len(suite.Checks) != 3 { - t.Fatalf("expected suite to contain %d checks, but had %d instead.", 3, len(suite.Checks)) - } - - suite.Process(context.TODO()) - - if suite.Passed() { - t.Fatalf("expected %s to fail, but it didn't", suite.Name) - } -} - -func TestBuildingChecksFromLoop(t *testing.T) { - suite := NewCheckSuite("passingChecks") - units := []string{"memory", "io", "disk"} - for _, u := range units { - // The closure function isn't evaluated until suite.Processed() is called. - // Iterators reuse the same pointer, so if you're passing a value into the anon function - // re-assign it to a new variable before hand. - unit := u - suite.AddCheck(unit, func() (string, error) { - return unit, nil - }) - } - - suite.Process(context.TODO()) - - resultArr := strings.Split(suite.RawResult(), "\n") - if len(resultArr) != len(units) { - t.Fatalf("expected resultArr to eq %d, got %d", len(units), len(resultArr)) - } - - r := strings.Join(sort.StringSlice(resultArr), "") - u := strings.Join(sort.StringSlice(units), "") - if r != u { - t.Fatalf("Expected slices to match. expected %q, received %q", u, r) - } - - if !suite.Passed() { - t.Fatalf("Expected check suite to pass") - } - -} - -func TestFailureDueToTimeout(t *testing.T) { - timeout := 200 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - suite := NewCheckSuite("timeoutChecks") - - suite.AddCheck("timeoutCheck-one", func() (string, error) { - return "passing", nil - }) - suite.AddCheck("timeoutCheck-two", func() (string, error) { - time.Sleep(300 * time.Millisecond) - return "passing", nil - }) - - go func() { - suite.Process(ctx) - cancel() - }() - - select { - case <-ctx.Done(): - if !errors.Is(ctx.Err(), context.DeadlineExceeded) { - t.Fatalf("expected context to fail with context deadline exceeded. received: %v", ctx.Err()) - } - if suite.Passed() { - t.Fatalf("expected suite to fail, but it passed instead.") - } - } -} - -func TestPartialSuccessWithTimeout(t *testing.T) { - timeout := 200 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - suite := NewCheckSuite("successWithTimeoutChecks") - suite.AddCheck("successWithTimeoutCheck-one", func() (string, error) { - return "passing", nil - }) - // Times out here. - suite.AddCheck("successWithTimeoutCheck-two", func() (string, error) { - time.Sleep(250 * time.Millisecond) - return "passing", nil - }) - // This check should not be processed. - suite.AddCheck("successWithTimeoutCheck-three", func() (string, error) { - time.Sleep(100 * time.Millisecond) - return "passing", nil - }) - - go func() { - suite.Process(ctx) - cancel() - }() - select { - case <-ctx.Done(): - - if !errors.Is(ctx.Err(), context.DeadlineExceeded) { - t.Fatalf("expected context to deadline, but instead received: %v", ctx.Err()) - } - if suite.Passed() { - t.Fatalf("check suite should not have passed...") - } - if suite.Checks[0].message != "passing" { - t.Fatalf("first check should have completed.") - } - if suite.Checks[1].startTime.IsZero() || !suite.Checks[1].endTime.IsZero() { - t.Fatalf("%s should have timed out.", suite.Checks[1].Name) - } - if !suite.Checks[2].startTime.IsZero() && !suite.Checks[2].endTime.IsZero() { - t.Fatalf("%s should not have been processed", suite.Checks[2].Name) - } - } -} - -func timeoutBeforeChecksHelper(ctx context.Context, suite *CheckSuite) *CheckSuite { - time.Sleep(200 * time.Millisecond) - - suite.AddCheck("setupTimeout-one", func() (string, error) { - return "passing", nil - }) - - suite.AddCheck("setupTimeout-two", func() (string, error) { - return "passing", nil - }) - - return suite -} - -func TestSetupTimeout(t *testing.T) { - timeout := 100 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - suite := NewCheckSuite("setupTimeout") - - go func(ctx context.Context) { - timeoutBeforeChecksHelper(ctx, suite) - suite.Process(ctx) - cancel() - }(ctx) - - select { - case <-ctx.Done(): - - if errors.Is(ctx.Err(), context.DeadlineExceeded) { - if len(suite.Checks) > 0 { - t.Fatalf("No checks should have been added") - } - } - } -} - -func TestOnCompletionHook(t *testing.T) { - target := "onCompletion" - myVar := "original" - - suite := NewCheckSuite("onCompletionTest") - - suite.OnCompletion = func() { - myVar = target - } - - suite.AddCheck("onCompletionTest-one", func() (string, error) { - myVar = "one" - return "passing", nil - }) - - suite.AddCheck("onCompletionTest-two", func() (string, error) { - myVar = "two" - return "passing", nil - }) - - suite.Process(context.TODO()) - - if myVar != target { - t.Fatalf("expected value to eq %s, instead got %s", target, myVar) - } -} diff --git a/pkg/check/check_test.go b/pkg/check/check_test.go deleted file mode 100644 index 55cbd2bf..00000000 --- a/pkg/check/check_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package check - -import ( - "fmt" - "testing" -) - -func TestPassingCheck(t *testing.T) { - chk := &Check{ - Name: "testCheck", - CheckFunc: func() (string, error) { - return "this worked", nil - }, - } - - chk.Process() - - if !chk.Passed() { - t.Fatalf("expected %s to pass, but the test failed with %s", chk.Name, chk.Error()) - } - - if chk.RawResult() != "this worked" { - t.Fatalf("expected %q to be %q", chk.RawResult(), "this worked") - } -} - -func TestFailingCheck(t *testing.T) { - chk := &Check{ - Name: "failCheck", - CheckFunc: func() (string, error) { - return "", fmt.Errorf("This check failed") - }, - } - chk.Process() - if chk.Passed() { - t.Fatalf("expected %s to pass, but the test failed with %s", chk.Name, chk.Error()) - } -} diff --git a/pkg/check/util.go b/pkg/check/util.go deleted file mode 100644 index 0a742a83..00000000 --- a/pkg/check/util.go +++ /dev/null @@ -1,18 +0,0 @@ -package check - -import "time" - -var divs = []time.Duration{ - time.Duration(1), time.Duration(10), time.Duration(100), time.Duration(1000)} - -func RoundDuration(d time.Duration, digits int) time.Duration { - switch { - case d > time.Second: - d = d.Round(time.Second / divs[digits]) - case d > time.Millisecond: - d = d.Round(time.Millisecond / divs[digits]) - case d > time.Microsecond: - d = d.Round(time.Microsecond / divs[digits]) - } - return d -} diff --git a/pkg/flycheck/checks.go b/pkg/flycheck/checks.go index 25b41e25..c0de2054 100644 --- a/pkg/flycheck/checks.go +++ b/pkg/flycheck/checks.go @@ -7,7 +7,7 @@ import ( "net/http" "time" - suite "github.com/fly-examples/postgres-ha/pkg/check" + suite "github.com/superfly/fly-checks/check" ) const Port = 5500 diff --git a/pkg/flycheck/pg.go b/pkg/flycheck/pg.go index 5f5a41ca..836f2086 100644 --- a/pkg/flycheck/pg.go +++ b/pkg/flycheck/pg.go @@ -7,10 +7,10 @@ import ( "strings" "time" - chk "github.com/fly-examples/postgres-ha/pkg/check" "github.com/fly-examples/postgres-ha/pkg/flypg" "github.com/fly-examples/postgres-ha/pkg/privnet" "github.com/pkg/errors" + chk "github.com/superfly/fly-checks/check" "github.com/jackc/pgtype" "github.com/jackc/pgx/v4" diff --git a/pkg/flycheck/role.go b/pkg/flycheck/role.go index 47546b0e..27203f31 100644 --- a/pkg/flycheck/role.go +++ b/pkg/flycheck/role.go @@ -3,8 +3,8 @@ package flycheck import ( "context" "fmt" + chk "github.com/superfly/fly-checks/check" - chk "github.com/fly-examples/postgres-ha/pkg/check" "github.com/fly-examples/postgres-ha/pkg/flypg" "github.com/fly-examples/postgres-ha/pkg/flypg/admin" "github.com/pkg/errors" diff --git a/pkg/flycheck/vm.go b/pkg/flycheck/vm.go index 4328aa55..a6417a82 100644 --- a/pkg/flycheck/vm.go +++ b/pkg/flycheck/vm.go @@ -10,7 +10,7 @@ import ( "syscall" "time" - "github.com/fly-examples/postgres-ha/pkg/check" + "github.com/superfly/fly-checks/check" ) // CheckVM for system / disk checks