| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "maps" |
| 5 | "path/filepath" |
| 6 | "reflect" |
| 7 | "sync" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostclaim" |
| 10 | ) |
| 11 | |
| 12 | type claimRestoreObservation struct { |
| 13 | mu sync.Mutex |
| 14 | observed hostclaim.Observed |
| 15 | box, dataDir, db string |
| 16 | } |
| 17 | |
| 18 | func sameClaimObservation(a, b hostclaim.Observed) bool { |
| 19 | return !a.Absent && !b.Absent && a.Host == b.Host && a.ETag != "" && a.ETag == b.ETag && reflect.DeepEqual(a.Claim, b.Claim) |
| 20 | } |
| 21 | |
| 22 | func (in ClaimLaunchInputs) observeClaimWrite(before, after hostclaim.Observed) error { |
| 23 | if in.claimWrite == nil || before.Claim == nil && after.Claim == nil { |
| 24 | return nil |
| 25 | } |
| 26 | return in.claimWrite(before, after) |
| 27 | } |
| 28 | |
| 29 | func (r *claimRestoreObservation) snapshot() hostclaim.Observed { |
| 30 | r.mu.Lock() |
| 31 | defer r.mu.Unlock() |
| 32 | return r.observed |
| 33 | } |
| 34 | |
| 35 | func (r *claimRestoreObservation) retag(before, after hostclaim.Observed) error { |
| 36 | return r.advance(before, after, func(expected *hostclaim.Claim) error { |
| 37 | db, err := filepath.Rel(r.dataDir, r.db) |
| 38 | if err != nil || !filepath.IsLocal(db) || expected.Holder == nil { |
| 39 | return hostclaim.ErrConflict |
| 40 | } |
| 41 | db = filepath.ToSlash(db) |
| 42 | entry := expected.Inventory[r.box] |
| 43 | old, left := entry.Paths[db] |
| 44 | marker, right := entry.Paths[db+".gen"] |
| 45 | if left != right || old != marker || left && (old == 0 || old >= expected.Holder.Stamp) { |
| 46 | return hostclaim.ErrConflict |
| 47 | } |
| 48 | expected.Inventory = maps.Clone(expected.Inventory) |
| 49 | if expected.Inventory == nil { |
| 50 | expected.Inventory = make(map[string]hostclaim.InventoryEntry) |
| 51 | } |
| 52 | entry.Paths = maps.Clone(entry.Paths) |
| 53 | if entry.Paths == nil { |
| 54 | entry.Paths = make(map[string]uint64) |
| 55 | } |
| 56 | if expected.Op.Kind == hostclaim.KindServing { |
| 57 | entry.Layout = "nested" |
| 58 | } |
| 59 | entry.Paths[db], entry.Paths[db+".gen"] = expected.Holder.Stamp, expected.Holder.Stamp |
| 60 | expected.Inventory[r.box] = entry |
| 61 | return nil |
| 62 | }) |
| 63 | } |
| 64 | |
| 65 | func (r *claimRestoreObservation) retry(before, after hostclaim.Observed) error { |
| 66 | return r.advance(before, after, func(expected *hostclaim.Claim) error { |
| 67 | if expected.Op.Kind != hostclaim.KindServing || expected.Holder == nil || after.Claim.Holder == nil { |
| 68 | return hostclaim.ErrConflict |
| 69 | } |
| 70 | holder := *expected.Holder |
| 71 | holder.WakeRetry = after.Claim.Holder.WakeRetry |
| 72 | expected.Holder = &holder |
| 73 | return nil |
| 74 | }) |
| 75 | } |
| 76 | |
| 77 | func (r *claimRestoreObservation) advance(before, after hostclaim.Observed, delta func(*hostclaim.Claim) error) error { |
| 78 | r.mu.Lock() |
| 79 | defer r.mu.Unlock() |
| 80 | if !sameClaimObservation(r.observed, before) || before.Claim == nil || after.Claim == nil || after.Absent || after.Host != before.Host || after.ETag == "" || after.ETag == before.ETag || after.Claim.Gen != before.Claim.Gen+1 { |
| 81 | return hostclaim.ErrConflict |
| 82 | } |
| 83 | expected := *before.Claim |
| 84 | expected.Gen++ |
| 85 | if err := delta(&expected); err != nil { |
| 86 | return err |
| 87 | } |
| 88 | if !reflect.DeepEqual(&expected, after.Claim) { |
| 89 | return hostclaim.ErrConflict |
| 90 | } |
| 91 | r.observed = after |
| 92 | return nil |
| 93 | } |
| 94 | |