| index 0fd8bc541..9d4f4c4a4 100644 |
| --- a/internal/park/remote_test.go |
| +++ b/internal/park/remote_test.go |
| @@ -4,6 +4,7 @@ import ( |
| "context" |
| "errors" |
| "fmt" |
| + "reflect" |
| "strings" |
| "testing" |
| "time" |
| @@ -299,6 +300,108 @@ func TestLiveGenerationCannotBeParked(t *testing.T) { |
| t.Fatal("live data changed") |
| } |
| } |
| + |
| +func TestRemoteMarkerDisagreementIsLocationScoped(t *testing.T) { |
| + for _, scenario := range []string{"old-incarnation", "live-location", "ancestor-scope", "ancestor-holder", "unknown-region", "unknown-marker", "unknown-generations", "unknown-pinned-generation", "view-error", "authorization-error"} { |
| + t.Run(scenario, func(t *testing.T) { |
| + f, obs, view, _ := sweepFixture(t) |
| + view.MarkerGeneration = "unrelated" |
| + f.ob.Op = "session:3" |
| + f.w.Op = f.ob.Op |
| + if scenario != "live-location" { |
| + f.ob.Park.Incarnation = strings.Repeat("a", 64) |
| + f.ob.Park.Location = "instances/" + f.w.Host + "/" + f.ob.Park.Incarnation |
| + } |
| + if scenario == "ancestor-scope" { |
| + f.ob.Park.Location = "instances/" + f.w.Host |
| + } |
| + _, err := hostclaim.Update(t.Context(), f.e.Claims, obs, func(c *hostclaim.Claim) error { |
| + c.Obligations = []hostclaim.Obligation{f.ob} |
| + if scenario == "ancestor-holder" { |
| + c.Holder.Location = "instances/" + f.w.Host |
| + } |
| + return nil |
| + }) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + source := f.seed() |
| + before := f.b.Snapshot() |
| + liveBefore, err := f.e.list(t.Context(), f.w.Host, obs.Claim.Holder.Location) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + switch scenario { |
| + case "unknown-region": |
| + view.AuthoritativeRegion = "" |
| + case "unknown-marker": |
| + view.MarkerKnown = false |
| + case "unknown-generations": |
| + view.GenerationOf = nil |
| + case "unknown-pinned-generation": |
| + view.GenerationOf = func(Ref) (string, bool) { return "", false } |
| + } |
| + f.e.View = func(_ context.Context, o hostclaim.Observed) (View, error) { |
| + v := view |
| + v.ClaimETag = o.ETag |
| + if scenario == "view-error" { |
| + return v, ErrUnsafe |
| + } |
| + return v, nil |
| + } |
| + authorized := false |
| + f.e.Authorize = func(_ context.Context, _ hostclaim.Observed, o hostclaim.Obligation) error { |
| + authorized = true |
| + if !reflect.DeepEqual(o, f.ob) || scenario == "authorization-error" { |
| + return ErrUnsafe |
| + } |
| + return nil |
| + } |
| + err = f.e.Run(t.Context(), f.w) |
| + if !authorized { |
| + t.Fatal("scope was not authorized") |
| + } |
| + if scenario != "old-incarnation" { |
| + if !errors.Is(err, ErrUnsafe) { |
| + t.Fatalf("Run = %v, want refusal", err) |
| + } |
| + if f.b.Snapshot() != before { |
| + t.Fatal("refused work changed storage") |
| + } |
| + pending, err := hostclaim.Read(t.Context(), f.e.Claims, f.w.Host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(pending.Claim.Obligations) != 1 || len(pending.Claim.QuietAfter) != 0 || f.held.released != 0 { |
| + t.Fatal("refused work acknowledged") |
| + } |
| + return |
| + } |
| + if err != nil { |
| + t.Fatalf("old incarnation refused by live marker disagreement: %v", err) |
| + } |
| + f.done(t) |
| + m, err := f.e.manifest(t.Context(), f.w, f.ob) |
| + if err != nil || m == nil || !reflect.DeepEqual(m.Delete, source) || len(m.Archive) != 2 { |
| + t.Fatalf("old incarnation manifest: %+v %v", m, err) |
| + } |
| + for _, copy := range m.Archive { |
| + body, err := f.b.GetVersion(t.Context(), "data", copy.Destination.Key, copy.Destination.VersionID) |
| + if err != nil || (string(body) != "old" && string(body) != "new") { |
| + t.Fatalf("archive: %q %v", body, err) |
| + } |
| + } |
| + liveAfter, err := f.e.list(t.Context(), f.w.Host, obs.Claim.Holder.Location) |
| + if err != nil || !reflect.DeepEqual(liveBefore, liveAfter) { |
| + t.Fatalf("live location changed: %v", err) |
| + } |
| + if f.held.released != 1 { |
| + t.Fatal("completed work did not release its lease") |
| + } |
| + }) |
| + } |
| +} |
| + |
| func TestBoundScopeAndQuietAfter(t *testing.T) { |
| f := newFixture(t) |
| obs, _ := hostclaim.Read(t.Context(), f.e.Claims, f.w.Host) |