| 1 | package hostclaim |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | "time" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/release" |
| 10 | ) |
| 11 | |
| 12 | func TestParkScopeAndHolderlessSweep(t *testing.T) { |
| 13 | c := &Claim{V: 2, Host: "shop.bab.example.com", Gen: 1, Op: Op{Kind: KindParking, ID: "sweep", Intent: map[string]string{"park_sweep": "v1"}}} |
| 14 | c.Obligations = []Obligation{{ID: "ob", Op: "sweep", Kind: "park-replica", Region: "ie", Retirement: "rt", Owner: "region:ie", Park: &ParkScope{Location: "instances/" + c.Host + "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Incarnation: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", EvidenceAt: time.Now(), RequestLimit: time.Second}}} |
| 15 | if err := c.Validate(); err != nil { |
| 16 | t.Fatal(err) |
| 17 | } |
| 18 | for _, change := range []func(*Claim){ |
| 19 | func(c *Claim) { c.Obligations[0].Park = nil }, |
| 20 | func(c *Claim) { c.Op.Exec = "box" }, |
| 21 | func(c *Claim) { c.Obligations[0].Op = "another" }, |
| 22 | func(c *Claim) { c.Op.Intent = nil }, |
| 23 | func(c *Claim) { c.Obligations[0].Park.RequestLimit = 0 }, |
| 24 | func(c *Claim) { c.Obligations[0].Park.EvidenceAt = time.Time{} }, |
| 25 | func(c *Claim) { c.Obligations[0].Park.Location = "instances/other.example" }, |
| 26 | } { |
| 27 | copy := *c |
| 28 | copy.Obligations = append([]Obligation(nil), c.Obligations...) |
| 29 | scope := *c.Obligations[0].Park |
| 30 | copy.Obligations[0].Park = &scope |
| 31 | change(©) |
| 32 | if err := copy.Validate(); err == nil { |
| 33 | t.Fatal("invalid sweep admitted") |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | func TestParkScopeLocationGrammar(t *testing.T) { |
| 39 | host := "shop.bab.example.com" |
| 40 | inc := strings.Repeat("a", 64) |
| 41 | instances, moves := "instances/"+host, "moves/"+host |
| 42 | for _, tc := range []struct { |
| 43 | location string |
| 44 | valid bool |
| 45 | }{ |
| 46 | {instances, true}, |
| 47 | {instances + "/" + inc, true}, |
| 48 | {moves, true}, |
| 49 | {moves + "/move-1", true}, |
| 50 | {moves + "/" + strings.Repeat("b", 64), true}, |
| 51 | {instances + "/", false}, |
| 52 | {instances + "/" + inc + "/", false}, |
| 53 | {instances + "/" + inc + "/x", false}, |
| 54 | {instances + "/" + strings.Repeat("b", 64), false}, |
| 55 | {moves + "/", false}, |
| 56 | {moves + "/move-1/", false}, |
| 57 | {moves + "/move-1/x", false}, |
| 58 | {moves + "/.", false}, |
| 59 | {moves + "/..", false}, |
| 60 | {moves + "//move-1", false}, |
| 61 | {"moves/other.example/move-1", false}, |
| 62 | {"instances/other.example", false}, |
| 63 | {"other/" + host, false}, |
| 64 | } { |
| 65 | t.Run(tc.location, func(t *testing.T) { |
| 66 | c := &Claim{V: 2, Host: host, Gen: 1, Op: Op{Kind: KindParking, ID: "sweep", Intent: map[string]string{"park_sweep": "v1"}}} |
| 67 | c.Obligations = []Obligation{{ID: "ob", Op: "sweep", Kind: "park-replica", Region: "ie", Retirement: "rt", Owner: "region:ie", Park: &ParkScope{Location: tc.location, Incarnation: inc, EvidenceAt: time.Now(), RequestLimit: time.Second}}} |
| 68 | err := c.Validate() |
| 69 | if tc.valid && err != nil { |
| 70 | t.Fatalf("valid scope rejected: %v", err) |
| 71 | } |
| 72 | if !tc.valid && !errors.Is(err, ErrMalformed) { |
| 73 | t.Fatalf("invalid scope: %v", err) |
| 74 | } |
| 75 | }) |
| 76 | } |
| 77 | for _, location := range []string{instances, instances + "/" + strings.ToUpper(inc), moves, moves + "/move-1"} { |
| 78 | t.Run("uppercase-archive/"+location, func(t *testing.T) { |
| 79 | c := &Claim{V: 2, Host: host, Gen: 1, Op: Op{Kind: KindParking, ID: "sweep", Intent: map[string]string{"park_sweep": "v1"}}} |
| 80 | c.Obligations = []Obligation{{ID: "ob", Op: "sweep", Kind: "park-replica", Region: "ie", Retirement: "rt", Owner: "region:ie", Park: &ParkScope{Location: location, Incarnation: strings.ToUpper(inc), EvidenceAt: time.Now(), RequestLimit: time.Second}}} |
| 81 | if err := c.Validate(); !errors.Is(err, ErrMalformed) { |
| 82 | t.Fatalf("uppercase archive incarnation accepted: %v", err) |
| 83 | } |
| 84 | }) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | func TestHolderlessSweepJoinsRegion(t *testing.T) { |
| 89 | host := "shop.bab.example.com" |
| 90 | st := release.NewDirStore(t.TempDir()) |
| 91 | chore := Obligation{ID: "ie-ob", Op: "sweep", Kind: "park-replica", Region: "ie", Retirement: "ie-rt", Owner: "region:ie", Park: &ParkScope{Location: "moves/" + host, Incarnation: strings.Repeat("a", 64), EvidenceAt: time.Now(), RequestLimit: time.Second}} |
| 92 | obs, err := Update(t.Context(), st, Observed{Host: host, Absent: true}, func(c *Claim) error { |
| 93 | c.Op = Op{Kind: KindParking, ID: "sweep", Intent: map[string]string{"park_sweep": "v1"}} |
| 94 | c.Obligations = []Obligation{chore} |
| 95 | return nil |
| 96 | }) |
| 97 | if err != nil { |
| 98 | t.Fatal(err) |
| 99 | } |
| 100 | joined := chore |
| 101 | joined.ID, joined.Region, joined.Retirement, joined.Owner = "uk-ob", "uk", "uk-rt", "region:uk" |
| 102 | obs, err = Update(t.Context(), st, obs, func(c *Claim) error { |
| 103 | c.Obligations = append(c.Obligations, joined) |
| 104 | return nil |
| 105 | }) |
| 106 | if err != nil { |
| 107 | t.Fatal(err) |
| 108 | } |
| 109 | if obs.Claim.Free() || obs.Claim.Holder != nil || obs.Claim.Op.ID != "sweep" || len(obs.Claim.Obligations) != 2 { |
| 110 | t.Fatalf("invalid joined sweep: %+v", obs.Claim) |
| 111 | } |
| 112 | _, err = Update(t.Context(), st, obs, func(c *Claim) error { |
| 113 | c.Obligations[1].Op = "other-sweep" |
| 114 | return nil |
| 115 | }) |
| 116 | if !errors.Is(err, ErrMalformed) { |
| 117 | t.Fatalf("joined region with a different op accepted: %v", err) |
| 118 | } |
| 119 | } |
| 120 | |