package hostops import ( "context" "encoding/json" "errors" "testing" "time" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" ) func TestJ17ReviewSidecarCannotBecomeInstanceRecord(t *testing.T) { ctx := t.Context() w := newJ17World(t, nil) c := w.claim(t).Claim host := provision.SidecarHost("worker", "app", "bab") key := provision.SidecarRecordKey("bab", "app", "worker") inc, err := hostclaim.Incarnation("bab", "app", host, w.now) if err != nil { t.Fatal(err) } c.Host = host c.Holder.Family = "sidecar" c.Holder.Name = "worker" c.Holder.Key = key c.Holder.Incarnation = inc c.Holder.Location = "instances/" + host if err := c.Validate(); err != nil { t.Fatal(err) } body, _ := json.Marshal(c) ck, _ := hostclaim.Key(host) ctag, err := w.raw.Put(ctx, ck, body, "") if err != nil { t.Fatal(err) } rec, _ := j17Record(t, w) rec.Host = host rec.Move.Incarnation = inc body, _ = json.Marshal(rec) var fields map[string]any if err := json.Unmarshal(body, &fields); err != nil { t.Fatal(err) } fields["name"] = "worker" body, _ = json.Marshal(fields) tag, err := w.raw.Put(ctx, key, body, "") if err != nil { t.Fatal(err) } a, err := ReadAdmission(ctx, w.raw, "bab", "app", "canonical") if err != nil { t.Fatal(err) } reg := a.Value.Registrations["move"] reg.Host, reg.Incarnation = host, inc a.Value.Registrations["move"] = reg ab, _ := json.Marshal(a.Value) if _, err := w.raw.Put(ctx, a.Key, ab, a.ETag); err != nil { t.Fatal(err) } if _, err := ObserveConsoleMove(ctx, w.raw, "bab", "app", host); !errors.Is(err, ErrIdentity) { t.Fatalf("sidecar observation: %v", err) } o := ConsoleMoveObservation{Account: "bab", App: "app", AppID: "canonical", Host: host, Incarnation: inc, OpID: "move", ClaimETag: ctag, RecordETag: tag} if _, err := w.control.ConsoleMove(ctx, o, ConsoleMoveAbort); !errors.Is(err, ErrIdentity) { t.Fatalf("sidecar action: %v", err) } after, afterTag, err := w.raw.Get(ctx, key) if err != nil || afterTag != tag || string(after) != string(body) { t.Fatal("sidecar record rewritten", err) } } type j17ClaimReadHook struct { release.Store key string reads int beforeSecond func() } func (s *j17ClaimReadHook) Get(ctx context.Context, key string) ([]byte, string, error) { if key == s.key { s.reads++ if s.reads == 2 { s.beforeSecond() } } return s.Store.Get(ctx, key) } func TestJ17ReviewClaimCheckedAtTheFinalWrite(t *testing.T) { ctx := t.Context() w := newJ17World(t, nil) o := j17Observe(t, w) key, _ := hostclaim.Key(w.host) hook := &j17ClaimReadHook{Store: w.raw, key: key, beforeSecond: func() { if _, err := hostclaim.Update(ctx, w.raw, w.claim(t), func(*hostclaim.Claim) error { return nil }); err != nil { t.Fatal(err) } }} cw, err := NewControlWriter(hook, w.pub, func() time.Time { return w.clock }) if err != nil { t.Fatal(err) } _, tag := j17Record(t, w) if _, err := cw.ConsoleMove(ctx, o, ConsoleMoveAbort); !errors.Is(err, hostclaim.ErrConflict) { t.Fatal(err) } if _, after := j17Record(t, w); after != tag { t.Fatal("stale claim wrote the record") } }