| 1 | //go:build linux |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "reflect" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostbox" |
| 10 | "github.com/carlosframework/platform/internal/hostclaim" |
| 11 | "github.com/carlosframework/platform/internal/hostguard" |
| 12 | ) |
| 13 | |
| 14 | // Startup resumes only an existing immutable intent for its active successor. |
| 15 | // A newer successor needs the ordinary request's stopped-continuation checks. |
| 16 | // Incomplete paired retagging remains the independent claim writer's work. |
| 17 | func (p *servingParkRoot) recoverServingPark(ctx context.Context, saved servingParkRecord) error { |
| 18 | active := saved.activeAuthorization() |
| 19 | return p.withRootInstallation(ctx, active.Successor, ctx.Err, func(o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, dir servingArtifactDirectory, tags uint64) error { |
| 20 | r, err := p.ledger.read(saved.Authorization.ID) |
| 21 | if err != nil || !reflect.DeepEqual(saved, r) || r.Complete || active.Directory != dir || (tags != active.TagsFrom && tags != active.Successor.Stamp) { |
| 22 | return hostbox.ErrChanged |
| 23 | } |
| 24 | if err = p.validateServingRecoveryAuthority(ctx, o, view, actor, r); err != nil { |
| 25 | return err |
| 26 | } |
| 27 | r, err = p.ledger.parkWithEvidenceLocked(ctx, r.Authorization, r.Receipts, r.LegacyEvidence) |
| 28 | if err != nil { |
| 29 | return err |
| 30 | } |
| 31 | if tags != active.Successor.Stamp { |
| 32 | return errServingParkRetagPending |
| 33 | } |
| 34 | return p.ledger.completeLocked(ctx, r.Authorization.ID, active.Successor) |
| 35 | }) |
| 36 | } |
| 37 | |
| 38 | func (p *servingParkRoot) recoverServingParks(ctx context.Context, after string, limit int) (servingParkRecoveryPage, error) { |
| 39 | var out servingParkRecoveryPage |
| 40 | if p == nil || p.ledger == nil { |
| 41 | return out, hostbox.ErrUnsupported |
| 42 | } |
| 43 | if after == "" { |
| 44 | err := p.ledger.withNamespace(ctx, func() error { |
| 45 | fs, ok := p.ledger.fs.(*servingParkFS) |
| 46 | if !ok { |
| 47 | return hostbox.ErrUnsupported |
| 48 | } |
| 49 | if err := fs.recoverRecordedNamespace(ctx); err == nil { |
| 50 | return nil |
| 51 | } |
| 52 | observation, err := fs.inspectRebootTopology(ctx) |
| 53 | if err != nil { |
| 54 | return err |
| 55 | } |
| 56 | binding := servingParkBootBinding{Version: 1, ID: "namespace", BeforeBoot: observation.Previous.BootID, BootID: observation.BootID, BeforeParent: observation.Previous.Parent, Parent: observation.CurrentParent} |
| 57 | if err := ctx.Err(); err != nil { |
| 58 | return err |
| 59 | } |
| 60 | if err := p.ledger.write("current-namespace.json", binding); err != nil { |
| 61 | return err |
| 62 | } |
| 63 | return fs.recoverRecordedNamespace(ctx) |
| 64 | }) |
| 65 | if err != nil { |
| 66 | out.Results = append(out.Results, servingParkRecoveryResult{Host: "unknown", ID: "namespace", Err: err}) |
| 67 | } |
| 68 | } |
| 69 | ids, more, err := p.ledger.recoveryIDs(ctx, after, limit) |
| 70 | if err != nil { |
| 71 | return out, err |
| 72 | } |
| 73 | out.More = more |
| 74 | for _, id := range ids { |
| 75 | if err = ctx.Err(); err != nil { |
| 76 | out.More = true |
| 77 | return out, err |
| 78 | } |
| 79 | r, readErr := p.ledger.read(id) |
| 80 | result := servingParkRecoveryResult{Host: "unknown", ID: id, Err: readErr} |
| 81 | if readErr == nil { |
| 82 | result.Host = r.Authorization.Successor.Host |
| 83 | fs, ok := p.ledger.fs.(*servingParkFS) |
| 84 | parentChanged := false |
| 85 | if ok { |
| 86 | parent, id, e := fs.parent() |
| 87 | if e != nil { |
| 88 | result.Err = e |
| 89 | } else { |
| 90 | parent.Close() |
| 91 | parentChanged = id != r.Source.Parent |
| 92 | } |
| 93 | } |
| 94 | if ok && result.Err == nil && r.Source.BootID != "" && (fs.checkBoot(r.Source.BootID) != nil || parentChanged) { |
| 95 | result.Err = p.commitServingReboot(ctx, id) |
| 96 | if result.Err == nil { |
| 97 | r, result.Err = p.ledger.read(id) |
| 98 | } |
| 99 | } |
| 100 | switch { |
| 101 | case result.Err != nil: |
| 102 | |
| 103 | case !r.Complete: |
| 104 | result.Err = p.recoverServingPark(ctx, r) |
| 105 | default: |
| 106 | result.Err = p.ledger.recoverCompleted(ctx, id) |
| 107 | } |
| 108 | } |
| 109 | out.Results = append(out.Results, result) |
| 110 | out.Next = id |
| 111 | } |
| 112 | return out, nil |
| 113 | } |
| 114 | |
| 115 | func (p *servingParkRoot) validateServingRecoveryAuthority(ctx context.Context, o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, r servingParkRecord) error { |
| 116 | active := r.activeAuthorization() |
| 117 | if err := validateServingRootHistory(ctx, o, view, actor, r); err != nil { |
| 118 | return err |
| 119 | } |
| 120 | // Any main-role intent is ambiguous at startup, even if a start reply |
| 121 | // was lost. Never resume a rename beneath a potentially live successor. |
| 122 | if err := p.ledger.legacyUnlaunched(ctx, active.Successor, actor.Enrollment); err != nil { |
| 123 | return err |
| 124 | } |
| 125 | if r.Authorization.Legacy { |
| 126 | consumed, e := p.ledger.legacyConsumption(active.Successor) |
| 127 | if e != nil || consumed == nil || !sameServingLegacyConsumption(*consumed, r) { |
| 128 | return hostbox.ErrChanged |
| 129 | } |
| 130 | if err := p.validateLegacyRootEvidence(ctx, o, view, actor, r.Authorization, r.LegacyEvidence, r.Receipts); err != nil { |
| 131 | return err |
| 132 | } |
| 133 | } else { |
| 134 | prior, e := servingPredecessor(o.Claim, active.Successor, r.Authorization.Predecessor.Stamp, r.Authorization.Predecessor.Generation) |
| 135 | if e != nil || prior != r.Authorization.Predecessor { |
| 136 | return hostbox.ErrChanged |
| 137 | } |
| 138 | receipts, e := servingRootStops(ctx, view, prior, actor) |
| 139 | if e != nil || !sameServingRootStops(receipts, r.Receipts) { |
| 140 | return ErrClaimStopReceipt |
| 141 | } |
| 142 | disposition, e := servingGenerationParkDisposition(ctx, p.ledger.journal, prior, receipts, r.Source.Files[0].Present) |
| 143 | if e != nil || disposition != r.Disposition { |
| 144 | return hostbox.ErrChanged |
| 145 | } |
| 146 | } |
| 147 | return nil |
| 148 | } |
| 149 | |