| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "os" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/instancectl" |
| 9 | ) |
| 10 | |
| 11 | // Placement comes from retained root state. Configuration may deny a new |
| 12 | // start, but removing it cannot retarget cleanup to the ordinary run unit. |
| 13 | type claimUnitDispatch struct { |
| 14 | base instancectl.ClaimUnits |
| 15 | bindings *claimUnitBindings |
| 16 | shapes func() (map[string]claimUnitShape, error) |
| 17 | definition func(claimUnitShape) error |
| 18 | effective func(context.Context, claimUnitShape) error |
| 19 | } |
| 20 | |
| 21 | func (d claimUnitDispatch) selected(host string) (instancectl.ClaimUnits, error) { |
| 22 | u := d.base |
| 23 | if d.bindings == nil || d.shapes == nil { |
| 24 | return u, os.ErrPermission |
| 25 | } |
| 26 | b, err := d.bindings.read(host) |
| 27 | if err == nil { |
| 28 | u.BoundHost, u.BoundUnit, u.BoundAddr = host, b.Shape.Unit, b.Shape.Addr |
| 29 | return u, nil |
| 30 | } |
| 31 | if !errors.Is(err, os.ErrNotExist) { |
| 32 | return u, err |
| 33 | } |
| 34 | |
| 35 | return u, nil |
| 36 | } |
| 37 | |
| 38 | func (d claimUnitDispatch) forStart(ctx context.Context, host string) (instancectl.ClaimUnits, error) { |
| 39 | u, err := d.selected(host) |
| 40 | if err != nil { |
| 41 | return u, err |
| 42 | } |
| 43 | if u.BoundUnit == "" { |
| 44 | shapes, e := d.shapes() |
| 45 | if e != nil { |
| 46 | return u, e |
| 47 | } |
| 48 | if e == nil { |
| 49 | if _, configured := shapes[host]; configured { |
| 50 | return u, os.ErrPermission |
| 51 | } |
| 52 | } |
| 53 | if d.effective == nil { |
| 54 | return u, os.ErrPermission |
| 55 | } |
| 56 | if err := d.effective(ctx, claimUnitShape{Host: host, Unit: instancectl.UnitName(instancectl.RoleRun, host)}); err != nil { |
| 57 | return u, err |
| 58 | } |
| 59 | } |
| 60 | if u.BoundUnit != "" { |
| 61 | shapes, err := d.shapes() |
| 62 | if err != nil { |
| 63 | return u, err |
| 64 | } |
| 65 | b, err := d.bindings.check(ctx, host, nil) |
| 66 | if err != nil { |
| 67 | return u, err |
| 68 | } |
| 69 | if shapes[host] != b.Shape { |
| 70 | return u, os.ErrPermission |
| 71 | } |
| 72 | if d.definition == nil { |
| 73 | return u, os.ErrPermission |
| 74 | } |
| 75 | if err := d.definition(b.Shape); err != nil { |
| 76 | return u, err |
| 77 | } |
| 78 | if d.effective != nil { |
| 79 | if err := d.effective(ctx, b.Shape); err != nil { |
| 80 | return u, err |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | return u, nil |
| 85 | } |
| 86 | |
| 87 | func (d claimUnitDispatch) Placement(host string) (string, string, error) { |
| 88 | u, err := d.selected(host) |
| 89 | if err != nil { |
| 90 | return "", "", err |
| 91 | } |
| 92 | return u.Placement(host) |
| 93 | } |
| 94 | |
| 95 | func (d claimUnitDispatch) StartReserved(ctx context.Context, in instancectl.ClaimUnit, l instancectl.Launch, p instancectl.ClaimExecPermit, check func() error) (instancectl.ClaimUnitStarted, error) { |
| 96 | u, err := d.forStart(ctx, in.Host) |
| 97 | if err != nil { |
| 98 | return instancectl.ClaimUnitStarted{}, err |
| 99 | } |
| 100 | if check == nil { |
| 101 | return instancectl.ClaimUnitStarted{}, os.ErrPermission |
| 102 | } |
| 103 | return u.StartReserved(ctx, in, l, p, func() error { |
| 104 | again, err := d.forStart(ctx, in.Host) |
| 105 | if err != nil { |
| 106 | return err |
| 107 | } |
| 108 | if again.BoundHost != u.BoundHost || again.BoundUnit != u.BoundUnit { |
| 109 | return os.ErrPermission |
| 110 | } |
| 111 | return check() |
| 112 | }) |
| 113 | } |
| 114 | |
| 115 | func (d claimUnitDispatch) Same(ctx context.Context, started instancectl.ClaimUnitStarted) error { |
| 116 | // The caller retained the selected placement in the launch grant. Same |
| 117 | // checks that exact invocation without taking a new route from the registry. |
| 118 | return d.base.Same(ctx, started) |
| 119 | } |
| 120 | |
| 121 | func (d claimUnitDispatch) Stop(context.Context, string) error { |
| 122 | // Dispatch cannot guess which invocation a bare host means. |
| 123 | return os.ErrPermission |
| 124 | } |
| 125 | |
| 126 | func (d claimUnitDispatch) StopInvocation(ctx context.Context, host, invocation string) error { |
| 127 | u, err := d.selected(host) |
| 128 | if err != nil { |
| 129 | return err |
| 130 | } |
| 131 | return u.StopInvocation(ctx, host, invocation) |
| 132 | } |
| 133 | |
| 134 | func (d claimUnitDispatch) Gone(ctx context.Context, host, invocation string) error { |
| 135 | u, err := d.selected(host) |
| 136 | if err != nil { |
| 137 | return err |
| 138 | } |
| 139 | return u.Gone(ctx, host, invocation) |
| 140 | } |
| 141 | |