| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "reflect" |
| 7 | "time" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostactors" |
| 10 | "github.com/carlosframework/platform/internal/hostbox" |
| 11 | "github.com/carlosframework/platform/internal/hostguard" |
| 12 | "github.com/carlosframework/platform/internal/release" |
| 13 | ) |
| 14 | |
| 15 | // Only a checked root journal reaches this collector. The legacy importer still |
| 16 | // requires its independently signed inventory proof before writing that journal. |
| 17 | // Offline migration observes closed, settled writers; it never kills from a |
| 18 | // legacy candidate or treats reboot alone as producer completion. |
| 19 | type migrationStopKernel struct { |
| 20 | StopKernelVerifier |
| 21 | launches *hostbox.LaunchJournal |
| 22 | inventory *hostbox.StopInventoryDB |
| 23 | roots map[string]string |
| 24 | } |
| 25 | |
| 26 | func (k migrationStopKernel) RecoverStopped(ctx context.Context, target hostguard.FenceTarget, inventory StopInventory) (StopKernelEvidence, DrainAcknowledgement, error) { |
| 27 | fail := func(err error) (StopKernelEvidence, DrainAcknowledgement, error) { |
| 28 | return StopKernelEvidence{}, DrainAcknowledgement{}, err |
| 29 | } |
| 30 | launch, err := k.launches.Read(ctx, target) |
| 31 | if err != nil { |
| 32 | return fail(err) |
| 33 | } |
| 34 | before, err := k.launches.ReadStopKernelJournal(ctx, target) |
| 35 | if err != nil { |
| 36 | return fail(err) |
| 37 | } |
| 38 | boot, err := recoveryControllerAt("/proc", target, before) |
| 39 | if err != nil { |
| 40 | return fail(err) |
| 41 | } |
| 42 | if err := verifyMigrationStopped(ctx, k.launches, k.inventory, launch, k.roots); err != nil { |
| 43 | return fail(err) |
| 44 | } |
| 45 | actual, err := k.inventory.ReadExactStopInventory(ctx, target) |
| 46 | if err != nil { |
| 47 | return fail(err) |
| 48 | } |
| 49 | if !reflect.DeepEqual(actual, inventory) { |
| 50 | return fail(ErrClaimStopInventory) |
| 51 | } |
| 52 | var groups []string |
| 53 | if boot == target.BootID { |
| 54 | groups, err = scanStopCgroups(ctx, "/sys/fs/cgroup", before.Cgroups) |
| 55 | } else { |
| 56 | groups, err = scanRebootCgroups(ctx, "/sys/fs/cgroup", before.Cgroups, target.BootID, boot) |
| 57 | } |
| 58 | if err != nil { |
| 59 | return fail(err) |
| 60 | } |
| 61 | after, err := k.launches.ReadStopKernelJournal(ctx, target) |
| 62 | if err != nil { |
| 63 | return fail(err) |
| 64 | } |
| 65 | if !reflect.DeepEqual(before, after) { |
| 66 | return fail(ErrClaimStopInventory) |
| 67 | } |
| 68 | ack := DrainClaimAcknowledgement(target.FenceID, target.ControllerID, before.ProcessStart, before.ProducerIDs, before.Completed) |
| 69 | return StopKernelEvidence{ObservedBootID: boot, InventorySHA256: digestStop(actual), LocalJournalSHA256: digestStop(after), CgroupIdentities: groups}, ack, nil |
| 70 | } |
| 71 | |
| 72 | func recoverMigrationStop(ctx context.Context, c ClaimStopCoordinator, store release.Store, directory hostactors.Directory, launches *hostbox.LaunchJournal, inventory *hostbox.StopInventoryDB, launch hostbox.LaunchInventory, claimETag string, roots map[string]string) (StopResult, error) { |
| 73 | result, err := readMigrationStop(ctx, store, directory, launches, inventory, launch) |
| 74 | fresh := errors.Is(err, hostbox.ErrLegacyJournalProof) |
| 75 | if err != nil && !fresh { |
| 76 | return StopResult{}, err |
| 77 | } |
| 78 | target := result.Target |
| 79 | if fresh { |
| 80 | target = launch.Target |
| 81 | target.FenceID = c.NewID() |
| 82 | } else if err := verifyMigrationStopped(ctx, launches, inventory, launch, roots); err != nil { |
| 83 | return StopResult{}, err |
| 84 | } |
| 85 | target.ClaimETag = claimETag |
| 86 | target, err = c.Guard.BeginFence(ctx, target) |
| 87 | if err != nil { |
| 88 | return StopResult{}, err |
| 89 | } |
| 90 | if receipt, err := c.Guard.FenceReceiptFor(ctx, target); err == nil { |
| 91 | return StopResult{Target: target, Bytes: receipt.Bytes, EvidenceAt: receipt.EvidenceAt}, nil |
| 92 | } else if !errors.Is(err, hostguard.ErrNoFenceReceipt) { |
| 93 | return StopResult{}, err |
| 94 | } |
| 95 | if fresh { |
| 96 | c.Kernel = migrationStopKernel{launches: launches, inventory: inventory, roots: roots} |
| 97 | return c.RecoverStopped(ctx, target) |
| 98 | } |
| 99 | if err := c.Guard.CompleteFence(ctx, target, result.Bytes, result.EvidenceAt, func(context.Context, hostguard.FenceTarget, []byte, time.Time) error { return nil }); err != nil { |
| 100 | return StopResult{}, err |
| 101 | } |
| 102 | result.Target = target |
| 103 | return result, nil |
| 104 | } |
| 105 | |