Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup integration test suite for db package #387

Merged
merged 6 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
version: v1.31
- name: go test
run: go test -coverprofile=coverage.txt ./...
run: go test -coverprofile=coverage.txt ./... ./db/_integration
- name: upload codecov
run: bash <(curl -s https://codecov.io/bash)
- uses: cachix/install-nix-action@v12
Expand Down
81 changes: 81 additions & 0 deletions db/_integration/db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package integration

import (
"context"
"database/sql"
"fmt"
"testing"
"time"

"github.com/packethost/pkg/log"
"github.com/pkg/errors"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"github.com/tinkerbell/tink/db"
)

type NewPostgresDatabaseRequest struct {
ApplyMigration bool
}

// NewPostgresDatabaseClient returns a SQL client ready to be used. Behind the
// scene it is starting a Docker container that will get cleaned up when the
// test is over. Tests using this function are safe to run in parallel
func NewPostgresDatabaseClient(t *testing.T, ctx context.Context, req NewPostgresDatabaseRequest) (*sql.DB, *db.TinkDB, func() error) {
postgresC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: "postgres:13.1",
ExposedPorts: []string{"5432/tcp"},
WaitingFor: wait.ForLog("database system is ready to accept connections"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add support to wait for something like wait.Exec and then we can just call pg_isready.

Env: map[string]string{
"POSTGRES_PASSWORD": "tinkerbell",
"POSTGRES_USER": "tinkerbell",
"POSTGRES_DB": "tinkerbell",
},
},
Started: true,
})
if err != nil {
t.Error(err)
}
port, err := postgresC.MappedPort(ctx, "5432")
if err != nil {
t.Error(err)
}
dbCon, err := sql.Open(
"postgres",
fmt.Sprintf(
"host=localhost port=%d user=%s password=%s dbname=%s sslmode=disable",
port.Int(),
"tinkerbell",
"tinkerbell",
"tinkerbell"))
if err != nil {
t.Error(err)
}

CHECK_DB:
for ii := 0; ii < 5; ii++ {
err = dbCon.Ping()
if err != nil {
t.Log(errors.Wrap(err, "db check"))
time.Sleep(1 * time.Second)
goto CHECK_DB
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this kind of doesn't make any sense right? If not ready go back to before the loop forever. If it is ready... make sure its ready 5 times?

}
if err != nil {
t.Fatal(err)
}

tinkDB := db.Connect(dbCon, log.Test(t, "db-test"))
if req.ApplyMigration {
n, err := tinkDB.Migrate()
if err != nil {
t.Error(err)
}
t.Log(fmt.Sprintf("applied %d migrations", n))
}
return dbCon, tinkDB, func() error {
return postgresC.Terminate(ctx)
}
}
215 changes: 215 additions & 0 deletions db/_integration/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package integration

import (
"context"
"fmt"
"math/rand"
"strings"
"sync"
"testing"
"time"

"github.com/golang/protobuf/ptypes/timestamp"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
_ "github.com/lib/pq"
"github.com/tinkerbell/tink/db"
"github.com/tinkerbell/tink/workflow"
"gopkg.in/yaml.v2"
)

var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))

func TestCreateTemplate(t *testing.T) {
ctx := context.Background()

table := []struct {
// Name identifies the single test in a table test scenario
Name string
// Input is a list of workflows that will be used to pre-populate the database
Input []*workflow.Workflow
// InputAsync if set to true inserts all the input concurrently
InputAsync bool
// Expectation is the function used to apply the assertions.
// You can use it to validate if the Input are created as you expect
Expectation func(*testing.T, []*workflow.Workflow, *db.TinkDB)
// ExpectedErr is used to check for error during
// CreateTemplate execution. If you expect a particular error
// and you want to assert it, you can use this function
ExpectedErr func(*testing.T, error)
}{
{
Name: "happy-path-single-crete-template",
Input: []*workflow.Workflow{
func() *workflow.Workflow {
w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
w.ID = "545f7ce9-5313-49c6-8704-0ed98814f1f7"
return w
}(),
},
Expectation: func(t *testing.T, input []*workflow.Workflow, tinkDB *db.TinkDB) {
wID, wName, wData, err := tinkDB.GetTemplate(context.Background(), map[string]string{"id": input[0].ID}, false)
if err != nil {
t.Error(err)
}
w := workflow.MustParse([]byte(wData))
w.ID = wID
w.Name = wName
if dif := cmp.Diff(input[0], w); dif != "" {
t.Errorf(dif)
}
},
},
{
Name: "create-two-template-same-name",
Input: []*workflow.Workflow{
func() *workflow.Workflow {
w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
w.ID = "545f7ce9-5313-49c6-8704-0ed98814f1f7"
return w
}(),
func() *workflow.Workflow {
w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
w.ID = "aaaaaaaa-5313-49c6-8704-bbbbbbbbbbbb"
return w
}(),
},
ExpectedErr: func(t *testing.T, err error) {
if err == nil {
t.Error("expected error, got nil")
}
if !strings.Contains(err.Error(), "pq: duplicate key value violates unique constraint \"uidx_template_name\"") {
t.Errorf("\nexpected err: %s\ngot: %s", "pq: duplicate key value violates unique constraint \"uidx_template_name\"", err)
}
},
},
{
Name: "update-on-create",
Input: []*workflow.Workflow{
func() *workflow.Workflow {
w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
w.ID = "545f7ce9-5313-49c6-8704-0ed98814f1f7"
return w
}(),
func() *workflow.Workflow {
w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
w.ID = "545f7ce9-5313-49c6-8704-0ed98814f1f7"
w.Name = "updated-name"
return w
}(),
},
Expectation: func(t *testing.T, input []*workflow.Workflow, tinkDB *db.TinkDB) {
_, wName, _, err := tinkDB.GetTemplate(context.Background(), map[string]string{"id": input[0].ID}, false)
if err != nil {
t.Error(err)
}
if wName != "updated-name" {
t.Errorf("expected name to be \"%s\", got \"%s\"", "updated-name", wName)
}
},
},
{
Name: "create-stress-test",
InputAsync: true,
Input: func() []*workflow.Workflow {
input := []*workflow.Workflow{}
for ii := 0; ii < 20; ii++ {
w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
w.ID = uuid.New().String()
w.Name = fmt.Sprintf("id_%d", rand.Int())
t.Log(w.Name)
input = append(input, w)
}
return input
}(),
ExpectedErr: func(t *testing.T, err error) {
if err != nil {
t.Error(err)
}
},
Expectation: func(t *testing.T, input []*workflow.Workflow, tinkDB *db.TinkDB) {
count := 0
err := tinkDB.ListTemplates("%", func(id, n string, in, del *timestamp.Timestamp) error {
count = count + 1
return nil
})
if err != nil {
t.Error(err)
}
if len(input) != count {
t.Errorf("expected %d templates stored in the database but we got %d", len(input), count)
}
},
},
}

for _, s := range table {
t.Run(s.Name, func(t *testing.T) {
t.Parallel()
_, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{
ApplyMigration: true,
})
defer cl()
var wg sync.WaitGroup
wg.Add(len(s.Input))
for _, tt := range s.Input {
if s.InputAsync {
go func(ctx context.Context, tinkDB *db.TinkDB, tt *workflow.Workflow) {
defer wg.Done()
err := createTemplateFromWorkflowType(ctx, tinkDB, tt)
if err != nil {
s.ExpectedErr(t, err)
}
}(ctx, tinkDB, tt)
} else {
wg.Done()
err := createTemplateFromWorkflowType(ctx, tinkDB, tt)
if err != nil {
s.ExpectedErr(t, err)
}
}
}
wg.Wait()
s.Expectation(t, s.Input, tinkDB)
})
}
}

func TestCreateTemplate_TwoTemplateWithSameNameButFirstOneIsDeleted(t *testing.T) {
t.Parallel()
ctx := context.Background()
_, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{
ApplyMigration: true,
})
defer cl()
w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
w.ID = "545f7ce9-5313-49c6-8704-0ed98814f1f7"
err := createTemplateFromWorkflowType(ctx, tinkDB, w)
if err != nil {
t.Error(err)
}
err = tinkDB.DeleteTemplate(ctx, w.ID)
if err != nil {
t.Error(err)
}

ww := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml")
ww.ID = "1111aaaa-5313-49c6-8704-222222aaaaaa"
err = createTemplateFromWorkflowType(ctx, tinkDB, ww)
if err != nil {
t.Error(err)
}
}

func createTemplateFromWorkflowType(ctx context.Context, tinkDB *db.TinkDB, tt *workflow.Workflow) error {
uID := uuid.MustParse(tt.ID)
content, err := yaml.Marshal(tt)
if err != nil {
return err
}
err = tinkDB.CreateTemplate(ctx, tt.Name, string(content), uID)
if err != nil {
return err
}
return nil
}
19 changes: 19 additions & 0 deletions db/_integration/testdata/template_happy_path_1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '0.1'
name: packet_osie_provision
global_timeout: 600
tasks:
- name: "run_one_worker"
worker: "{{.device_1}}"
environment:
MIRROR_HOST: 192.168.1.2
actions:
- name: "server_partitioning"
image: update-data
timeout: 60
environment:
NGINX_HOST: 192.168.1.2
- name: "update_db"
image: update-data
timeout: 50
environment:
MIRROR_HOST: 192.168.1.3
6 changes: 3 additions & 3 deletions db/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ func (d TinkDB) GetTemplate(ctx context.Context, fields map[string]string, delet
return "", "", "", err
}

// DeleteTemplate deletes a workflow template
func (d TinkDB) DeleteTemplate(ctx context.Context, name string) error {
// DeleteTemplate deletes a workflow template by id
func (d TinkDB) DeleteTemplate(ctx context.Context, id string) error {
tx, err := d.instance.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
return errors.Wrap(err, "BEGIN transaction")
Expand All @@ -97,7 +97,7 @@ func (d TinkDB) DeleteTemplate(ctx context.Context, name string) error {
deleted_at = NOW()
WHERE
id = $1;
`, name)
`, id)
if err != nil {
return errors.Wrap(err, "UPDATE")
}
Expand Down
12 changes: 4 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,22 @@ module github.com/tinkerbell/tink
go 1.13

require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/containerd/containerd v1.3.2 // indirect
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v1.4.2-0.20191212201129-5f9f41018e9d
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible
github.com/docker/go-units v0.4.0 // indirect
github.com/go-openapi/strfmt v0.19.3 // indirect
github.com/golang/protobuf v1.4.2
github.com/google/go-cmp v0.5.2 // indirect
github.com/google/go-cmp v0.5.2
github.com/google/uuid v1.1.2
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/grpc-gateway v1.15.2
github.com/jedib0t/go-pretty v4.3.0+incompatible
github.com/lib/pq v1.2.1-0.20191011153232-f91d3411e481
github.com/mattn/go-runewidth v0.0.5 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/packethost/pkg v0.0.0-20200903155310-0433e0605550
github.com/pkg/errors v0.8.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.3.0
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351
github.com/sirupsen/logrus v1.4.2
Expand All @@ -32,6 +27,7 @@ require (
github.com/spf13/viper v1.7.0
github.com/stormcat24/protodep v0.0.0-20200505140716-b02c9ba62816
github.com/stretchr/testify v1.6.1
github.com/testcontainers/testcontainers-go v0.9.0
go.mongodb.org/mongo-driver v1.1.2 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
Expand Down
Loading