Skip to content

Commit

Permalink
add chainlink health command; make DB avialable for testscript client…
Browse files Browse the repository at this point in the history
…/server tests (#11591)
  • Loading branch information
jmank88 committed Jan 4, 2024
1 parent 529d2cf commit 2f10153
Show file tree
Hide file tree
Showing 14 changed files with 658 additions and 74 deletions.
11 changes: 11 additions & 0 deletions core/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ func NewApp(s *Shell) *cli.App {
Usage: "Commands for the node's configuration",
Subcommands: initRemoteConfigSubCmds(s),
},
{
Name: "health",
Usage: "Prints a health report",
Action: s.Health,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "json, j",
Usage: "json output",
},
},
},
{
Name: "jobs",
Usage: "Commands for managing Jobs",
Expand Down
14 changes: 4 additions & 10 deletions core/cmd/shell_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/utils"
"github.com/smartcontractkit/chainlink/v2/core/web"
webPresenters "github.com/smartcontractkit/chainlink/v2/core/web/presenters"
"github.com/smartcontractkit/chainlink/v2/internal/testdb"
)

var ErrProfileTooLong = errors.New("requested profile duration too large")
Expand Down Expand Up @@ -258,13 +259,6 @@ func initLocalSubCmds(s *Shell, safe bool) []cli.Command {
// ownerPermsMask are the file permission bits reserved for owner.
const ownerPermsMask = os.FileMode(0o700)

// PristineDBName is a clean copy of test DB with migrations.
// Used by heavyweight.FullTestDB* functions.
const (
PristineDBName = "chainlink_test_pristine"
TestDBNamePrefix = "chainlink_test_"
)

// RunNode starts the Chainlink core.
func (s *Shell) RunNode(c *cli.Context) error {
if err := s.runNode(c); err != nil {
Expand Down Expand Up @@ -815,7 +809,7 @@ func dropDanglingTestDBs(lggr logger.Logger, db *sqlx.DB) (err error) {
}()
}
for _, dbname := range dbs {
if strings.HasPrefix(dbname, TestDBNamePrefix) && !strings.HasSuffix(dbname, "_pristine") {
if strings.HasPrefix(dbname, testdb.TestDBNamePrefix) && !strings.HasSuffix(dbname, "_pristine") {
ch <- dbname
}
}
Expand Down Expand Up @@ -1085,11 +1079,11 @@ func dropAndCreateDB(parsed url.URL) (err error) {
}

func dropAndCreatePristineDB(db *sqlx.DB, template string) (err error) {
_, err = db.Exec(fmt.Sprintf(`DROP DATABASE IF EXISTS "%s"`, PristineDBName))
_, err = db.Exec(fmt.Sprintf(`DROP DATABASE IF EXISTS "%s"`, testdb.PristineDBName))
if err != nil {
return fmt.Errorf("unable to drop postgres database: %v", err)
}
_, err = db.Exec(fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s"`, PristineDBName, template))
_, err = db.Exec(fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s"`, testdb.PristineDBName, template))
if err != nil {
return fmt.Errorf("unable to create postgres database: %v", err)
}
Expand Down
18 changes: 18 additions & 0 deletions core/cmd/shell_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strconv"
"strings"

"github.com/gin-gonic/gin"
"github.com/manyminds/api2go/jsonapi"
"github.com/mitchellh/go-homedir"
"github.com/pelletier/go-toml"
Expand Down Expand Up @@ -511,6 +512,23 @@ func (s *Shell) checkRemoteBuildCompatibility(lggr logger.Logger, onlyWarn bool,
return nil
}

func (s *Shell) Health(c *cli.Context) error {
mime := gin.MIMEPlain
if c.Bool("json") {
mime = gin.MIMEJSON
}
resp, err := s.HTTP.Get("/health", map[string]string{"Accept": mime})
if err != nil {
return s.errorOut(err)
}
b, err := parseResponse(resp)
if err != nil {
return s.errorOut(err)
}
fmt.Println(string(b))
return nil
}

// ErrIncompatible is returned when the cli and remote versions are not compatible.
type ErrIncompatible struct {
CLIVersion, CLISha string
Expand Down
71 changes: 17 additions & 54 deletions core/internal/cltest/heavyweight/orm.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
package heavyweight

// The heavyweight package contains cltest items that are costly and you should
// Package heavyweight contains test helpers that are costly and you should
// think **real carefully** before using in your tests.
package heavyweight

import (
"database/sql"
"errors"
"fmt"
"net/url"
"os"
"path"
"runtime"
Expand All @@ -20,41 +15,45 @@ import (

"github.com/jmoiron/sqlx"

"github.com/smartcontractkit/chainlink/v2/core/cmd"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/services/pg"
"github.com/smartcontractkit/chainlink/v2/core/store/dialects"
"github.com/smartcontractkit/chainlink/v2/core/store/models"
"github.com/smartcontractkit/chainlink/v2/internal/testdb"
)

// FullTestDBV2 creates a pristine DB which runs in a separate database than the normal
// unit tests, so you can do things like use other Postgres connection types with it.
func FullTestDBV2(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) {
return prepareFullTestDBV2(t, false, true, overrideFn)
return KindFixtures.PrepareDB(t, overrideFn)
}

// FullTestDBNoFixturesV2 is the same as FullTestDB, but it does not load fixtures.
func FullTestDBNoFixturesV2(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) {
return prepareFullTestDBV2(t, false, false, overrideFn)
return KindTemplate.PrepareDB(t, overrideFn)
}

// FullTestDBEmptyV2 creates an empty DB (without migrations).
func FullTestDBEmptyV2(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) {
return prepareFullTestDBV2(t, true, false, overrideFn)
return KindEmpty.PrepareDB(t, overrideFn)
}

func generateName() string {
return strings.ReplaceAll(uuid.New().String(), "-", "")
}

func prepareFullTestDBV2(t testing.TB, empty bool, loadFixtures bool, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) {
testutils.SkipShort(t, "FullTestDB")
type Kind int

if empty && loadFixtures {
t.Fatal("could not load fixtures into an empty DB")
}
const (
KindEmpty Kind = iota
KindTemplate
KindFixtures
)

func (c Kind) PrepareDB(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) {
testutils.SkipShort(t, "FullTestDB")

gcfg := configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) {
c.Database.Dialect = dialects.Postgres
Expand All @@ -64,7 +63,7 @@ func prepareFullTestDBV2(t testing.TB, empty bool, loadFixtures bool, overrideFn
})

require.NoError(t, os.MkdirAll(gcfg.RootDir(), 0700))
migrationTestDBURL, err := dropAndCreateThrowawayTestDB(gcfg.Database().URL(), generateName(), empty)
migrationTestDBURL, err := testdb.CreateOrReplace(gcfg.Database().URL(), generateName(), c != KindEmpty)
require.NoError(t, err)
db, err := pg.NewConnection(migrationTestDBURL, dialects.Postgres, gcfg.Database())
require.NoError(t, err)
Expand All @@ -81,7 +80,7 @@ func prepareFullTestDBV2(t testing.TB, empty bool, loadFixtures bool, overrideFn
}
})

if loadFixtures {
if c == KindFixtures {
_, filename, _, ok := runtime.Caller(1)
if !ok {
t.Fatal("could not get runtime.Caller(1)")
Expand All @@ -95,39 +94,3 @@ func prepareFullTestDBV2(t testing.TB, empty bool, loadFixtures bool, overrideFn

return gcfg, db
}

func dropAndCreateThrowawayTestDB(parsed url.URL, postfix string, empty bool) (string, error) {
if parsed.Path == "" {
return "", errors.New("path missing from database URL")
}

// Match the naming schema that our dangling DB cleanup methods expect
dbname := cmd.TestDBNamePrefix + postfix
if l := len(dbname); l > 63 {
return "", fmt.Errorf("dbname %v too long (%d), max is 63 bytes. Try a shorter postfix", dbname, l)
}
// Cannot drop test database if we are connected to it, so we must connect
// to a different one. 'postgres' should be present on all postgres installations
parsed.Path = "/postgres"
db, err := sql.Open(string(dialects.Postgres), parsed.String())
if err != nil {
return "", fmt.Errorf("In order to drop the test database, we need to connect to a separate database"+
" called 'postgres'. But we are unable to open 'postgres' database: %+v\n", err)
}
defer db.Close()

_, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbname))
if err != nil {
return "", fmt.Errorf("unable to drop postgres migrations test database: %v", err)
}
if empty {
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", dbname))
} else {
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s WITH TEMPLATE %s", dbname, cmd.PristineDBName))
}
if err != nil {
return "", fmt.Errorf("unable to create postgres test database with name '%s': %v", dbname, err)
}
parsed.Path = fmt.Sprintf("/%s", dbname)
return parsed.String(), nil
}
2 changes: 1 addition & 1 deletion core/web/health_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (hc *HealthController) Health(c *gin.Context) {
healthy, errors := checker.IsHealthy()

if !healthy {
status = http.StatusServiceUnavailable
status = http.StatusMultiStatus
}

c.Status(status)
Expand Down
4 changes: 2 additions & 2 deletions core/web/health_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestHealthController_Health_status(t *testing.T) {
{
name: "not ready",
ready: false,
status: http.StatusServiceUnavailable,
status: http.StatusMultiStatus,
},
{
name: "ready",
Expand Down Expand Up @@ -118,7 +118,7 @@ func TestHealthController_Health_body(t *testing.T) {
client := app.NewHTTPClient(nil)
resp, cleanup := client.Get(tc.path, tc.headers)
t.Cleanup(cleanup)
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
assert.Equal(t, http.StatusMultiStatus, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
if tc.expBody == bodyJSON {
Expand Down
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [dev]

### Added

- `chainlink health` CLI command and HTML `/health` endpoint, to provide human-readable views of the underlying JSON health data.

### Fixed

- Fixed the encoding used for transactions when resending in batches
Expand Down
56 changes: 56 additions & 0 deletions internal/testdb/testdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package testdb

import (
"database/sql"
"errors"
"fmt"
"net/url"

"github.com/smartcontractkit/chainlink/v2/core/store/dialects"
)

const (
// PristineDBName is a clean copy of test DB with migrations.
PristineDBName = "chainlink_test_pristine"
// TestDBNamePrefix is a common prefix that will be auto-removed by the dangling DB cleanup process.
TestDBNamePrefix = "chainlink_test_"
)

// CreateOrReplace creates a database named with a common prefix and the given suffix, and returns the URL.
// If the database already exists, it will be dropped and re-created.
// If withTemplate is true, the pristine DB will be used as a template.
func CreateOrReplace(parsed url.URL, suffix string, withTemplate bool) (string, error) {
if parsed.Path == "" {
return "", errors.New("path missing from database URL")
}

// Match the naming schema that our dangling DB cleanup methods expect
dbname := TestDBNamePrefix + suffix
if l := len(dbname); l > 63 {
return "", fmt.Errorf("dbname %v too long (%d), max is 63 bytes. Try a shorter suffix", dbname, l)
}
// Cannot drop test database if we are connected to it, so we must connect
// to a different one. 'postgres' should be present on all postgres installations
parsed.Path = "/postgres"
db, err := sql.Open(string(dialects.Postgres), parsed.String())
if err != nil {
return "", fmt.Errorf("in order to drop the test database, we need to connect to a separate database"+
" called 'postgres'. But we are unable to open 'postgres' database: %+v\n", err)
}
defer db.Close()

_, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbname))
if err != nil {
return "", fmt.Errorf("unable to drop postgres migrations test database: %v", err)
}
if withTemplate {
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s WITH TEMPLATE %s", dbname, PristineDBName))
} else {
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", dbname))
}
if err != nil {
return "", fmt.Errorf("unable to create postgres test database with name '%s': %v", dbname, err)
}
parsed.Path = fmt.Sprintf("/%s", dbname)
return parsed.String(), nil
}
Loading

0 comments on commit 2f10153

Please sign in to comment.