190 lines
6.8 KiB
Go
190 lines
6.8 KiB
Go
package db
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"cloudipvalidator/internal/config"
|
||
|
|
)
|
||
|
|
|
||
|
|
// claimAwaitingSelfCheck claims the next address for the validator and brings
|
||
|
|
// it to awaiting_self_check, as the orchestrator does after the association.
|
||
|
|
func claimAwaitingSelfCheck(t *testing.T, ctx context.Context, d *DB, validatorID string) *IPQueueItem {
|
||
|
|
t.Helper()
|
||
|
|
item, err := d.ClaimNextQueued(ctx, validatorID, time.Minute)
|
||
|
|
if err != nil || item == nil {
|
||
|
|
t.Fatalf("claim for %s: item=%+v err=%v", validatorID, item, err)
|
||
|
|
}
|
||
|
|
if err := d.SetFIPAssociated(ctx, item.ID, "fip-"+item.IPAddress, time.Minute); err != nil {
|
||
|
|
t.Fatalf("set fip associated: %v", err)
|
||
|
|
}
|
||
|
|
return item
|
||
|
|
}
|
||
|
|
|
||
|
|
// A validator that failed the self-check of an address is not given that
|
||
|
|
// address again, takes the next one instead, and another validator takes the
|
||
|
|
// excluded one; the exclusion covers that address only.
|
||
|
|
func TestClaimSkipsAddressExcludedForValidator(t *testing.T) {
|
||
|
|
d, ctx := newTestDB(t)
|
||
|
|
if err := d.SeedQueue(ctx, []string{"1.1.1.1", "2.2.2.2"}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, v := range []string{"v1", "v2"} {
|
||
|
|
if err := d.AdminCreateValidator(ctx, v, "port-"+v); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
first := claimAwaitingSelfCheck(t, ctx, d, "v1")
|
||
|
|
if first.IPAddress != "1.1.1.1" {
|
||
|
|
t.Fatalf("expected 1.1.1.1 first, got %s", first.IPAddress)
|
||
|
|
}
|
||
|
|
res, err := d.FailSelfCheck(ctx, first.ID, "v1", "ip echo timeout", 5)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("fail self-check: %v", err)
|
||
|
|
}
|
||
|
|
if res.Failed || res.NewRound || res.Failures != 1 {
|
||
|
|
t.Fatalf("expected a plain retry after the first failure, got %+v", res)
|
||
|
|
}
|
||
|
|
back, _ := d.GetIP(ctx, first.ID)
|
||
|
|
if back.State != IPQueued || back.RetryCount != 0 {
|
||
|
|
t.Fatalf("expected queued with retry_count untouched, got state=%s retry_count=%d", back.State, back.RetryCount)
|
||
|
|
}
|
||
|
|
if v, _ := d.GetValidator(ctx, "v1"); v.State != ValidatorIdle {
|
||
|
|
t.Fatalf("expected v1 freed to idle, got %s", v.State)
|
||
|
|
}
|
||
|
|
|
||
|
|
// v1 skips 1.1.1.1 (still ahead in the queue) and takes 2.2.2.2.
|
||
|
|
second, err := d.ClaimNextQueued(ctx, "v1", time.Minute)
|
||
|
|
if err != nil || second == nil || second.IPAddress != "2.2.2.2" {
|
||
|
|
t.Fatalf("expected v1 to take 2.2.2.2, got %+v err=%v", second, err)
|
||
|
|
}
|
||
|
|
// v2 takes the excluded address.
|
||
|
|
other, err := d.ClaimNextQueued(ctx, "v2", time.Minute)
|
||
|
|
if err != nil || other == nil || other.IPAddress != "1.1.1.1" {
|
||
|
|
t.Fatalf("expected v2 to take 1.1.1.1, got %+v err=%v", other, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// When every working validator has failed the address in the round, a new
|
||
|
|
// round starts and the exclusions lapse; an unreachable validator does not
|
||
|
|
// count as working.
|
||
|
|
func TestSelfCheckNewRoundLiftsExclusions(t *testing.T) {
|
||
|
|
d, ctx := newTestDB(t)
|
||
|
|
if err := d.SeedQueue(ctx, []string{"1.1.1.1"}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, v := range []string{"v1", "v2"} {
|
||
|
|
if err := d.AdminCreateValidator(ctx, v, "port-"+v); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if _, err := d.Exec(`UPDATE validators SET state=? WHERE validator_id='v2'`, ValidatorUnreachable); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
item := claimAwaitingSelfCheck(t, ctx, d, "v1")
|
||
|
|
res, err := d.FailSelfCheck(ctx, item.ID, "v1", "x", 5)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if !res.NewRound {
|
||
|
|
t.Fatalf("expected a new round (the only working validator failed it), got %+v", res)
|
||
|
|
}
|
||
|
|
again, err := d.ClaimNextQueued(ctx, "v1", time.Minute)
|
||
|
|
if err != nil || again == nil || again.IPAddress != "1.1.1.1" {
|
||
|
|
t.Fatalf("expected v1 to get the address again in the new round, got %+v err=%v", again, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reaching the ceiling gives the verdict fail; a re-submission starts a new
|
||
|
|
// series (counter and exclusions) but keeps the failure history.
|
||
|
|
func TestSelfCheckCeilingAndResubmit(t *testing.T) {
|
||
|
|
d, ctx := newTestDB(t)
|
||
|
|
if err := d.SeedQueue(ctx, []string{"1.1.1.1"}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, v := range []string{"v1", "v2"} {
|
||
|
|
if err := d.AdminCreateValidator(ctx, v, "port-"+v); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
item := claimAwaitingSelfCheck(t, ctx, d, "v1")
|
||
|
|
if res, err := d.FailSelfCheck(ctx, item.ID, "v1", "x", 2); err != nil || res.Failed {
|
||
|
|
t.Fatalf("below the ceiling: res=%+v err=%v", res, err)
|
||
|
|
}
|
||
|
|
item = claimAwaitingSelfCheck(t, ctx, d, "v2")
|
||
|
|
res, err := d.FailSelfCheck(ctx, item.ID, "v2", "y", 2)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if !res.Failed || res.Failures != 2 || len(res.Validators) != 2 || res.Validators[0] != "v1" || res.Validators[1] != "v2" {
|
||
|
|
t.Fatalf("expected fail at the ceiling on v1, v2, got %+v", res)
|
||
|
|
}
|
||
|
|
ip, _ := d.GetIP(ctx, item.ID)
|
||
|
|
if ip.State != IPFailed || ip.OverallResult != ResultFail {
|
||
|
|
t.Fatalf("expected failed/fail, got %s/%s", ip.State, ip.OverallResult)
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := d.SubmitIPs(ctx, []string{"1.1.1.1"}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var failures int
|
||
|
|
if err := d.QueryRow(`SELECT sc_failures FROM ip_queue WHERE id=?`, item.ID).Scan(&failures); err != nil || failures != 0 {
|
||
|
|
t.Fatalf("expected the series reset to 0, got %d err=%v", failures, err)
|
||
|
|
}
|
||
|
|
if got, err := d.ListSelfCheckFailedOn(ctx, ip.RegistryID, 0); err != nil || len(got) != 2 || got[0] != "v1" || got[1] != "v2" {
|
||
|
|
t.Fatalf("expected the history kept (v1, v2), got %v err=%v", got, err)
|
||
|
|
}
|
||
|
|
// v1 failed it before the re-submission, but is no longer excluded.
|
||
|
|
if again, err := d.ClaimNextQueued(ctx, "v1", time.Minute); err != nil || again == nil {
|
||
|
|
t.Fatalf("expected v1 to claim the re-submitted address, got %+v err=%v", again, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A late report for an address the validator does not hold is refused.
|
||
|
|
func TestFailSelfCheckRequiresAwaitingSelfCheckOnValidator(t *testing.T) {
|
||
|
|
d, ctx := newTestDB(t)
|
||
|
|
if err := d.SeedQueue(ctx, []string{"1.1.1.1"}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, v := range []string{"v1", "v2"} {
|
||
|
|
if err := d.AdminCreateValidator(ctx, v, "port-"+v); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
item := claimAwaitingSelfCheck(t, ctx, d, "v1")
|
||
|
|
if _, err := d.FailSelfCheck(ctx, item.ID, "v2", "late", 5); !errors.Is(err, ErrInvalidState) {
|
||
|
|
t.Fatalf("expected ErrInvalidState for another validator, got %v", err)
|
||
|
|
}
|
||
|
|
cur, _ := d.GetIP(ctx, item.ID)
|
||
|
|
if got, _ := d.ListSelfCheckFailedOn(ctx, cur.RegistryID, 0); len(got) != 0 {
|
||
|
|
t.Fatalf("a refused report must leave no history, got %v", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSelfCheckMaxAttemptsSetting(t *testing.T) {
|
||
|
|
d, ctx := newTestDB(t)
|
||
|
|
if err := d.BootstrapFromConfig(ctx, &config.ControlAPI{}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if s, err := d.GetSettings(ctx); err != nil || s.SelfCheckMaxAttempts != 5 {
|
||
|
|
t.Fatalf("expected default 5, got %+v err=%v", s, err)
|
||
|
|
}
|
||
|
|
for _, bad := range []int{0, -1, 51} {
|
||
|
|
if err := d.SetSelfCheckMaxAttempts(ctx, bad); !errors.Is(err, ErrValidation) {
|
||
|
|
t.Fatalf("expected ErrValidation for %d, got %v", bad, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, ok := range []int{1, 50} {
|
||
|
|
if err := d.SetSelfCheckMaxAttempts(ctx, ok); err != nil {
|
||
|
|
t.Fatalf("set %d: %v", ok, err)
|
||
|
|
}
|
||
|
|
if s, _ := d.GetSettings(ctx); s.SelfCheckMaxAttempts != ok {
|
||
|
|
t.Fatalf("expected %d, got %d", ok, s.SelfCheckMaxAttempts)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|