| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/hostguard" |
| 9 | "github.com/carlosframework/platform/internal/instancectl" |
| 10 | ) |
| 11 | |
| 12 | func TestPlan14UnitDispatch(t *testing.T) { |
| 13 | b, in, shape := plan14UnitBindingFixture(t, "console.service", "", "") |
| 14 | configured := map[string]claimUnitShape{in.Host: shape} |
| 15 | d := claimUnitDispatch{bindings: b, shapes: func() (map[string]claimUnitShape, error) { return configured, nil }} |
| 16 | if _, err := d.forStart(t.Context(), in.Host); err == nil { |
| 17 | t.Fatal("configured unit without retained binding fell through") |
| 18 | } |
| 19 | if _, err := b.establish(t.Context(), in.Host); err != nil { |
| 20 | t.Fatal(err) |
| 21 | } |
| 22 | unit, group, err := d.Placement(in.Host) |
| 23 | if err != nil || unit != shape.Unit || group != instancectl.BoundClaimUnitControlGroup(in.Host, shape.Unit) { |
| 24 | t.Fatal(unit, group, err) |
| 25 | } |
| 26 | unit, group, err = d.Placement("sibling.example") |
| 27 | if err != nil || unit != instancectl.UnitName(instancectl.RoleRun, "sibling.example") || group != instancectl.ClaimUnitControlGroup("sibling.example") { |
| 28 | t.Fatal(unit, group, err) |
| 29 | } |
| 30 | delete(configured, in.Host) |
| 31 | if _, err := d.forStart(t.Context(), in.Host); err == nil { |
| 32 | t.Fatal("removed configuration allowed start") |
| 33 | } |
| 34 | unit, _, err = d.Placement(in.Host) |
| 35 | if err != nil || unit != shape.Unit { |
| 36 | t.Fatal("cleanup lost retained placement", unit, err) |
| 37 | } |
| 38 | configured[in.Host] = shape |
| 39 | changed := in |
| 40 | changed.Stamp++ |
| 41 | b.installation = func(context.Context, string) (hostguard.RootInstallation, error) { return changed, nil } |
| 42 | if _, err := d.forStart(t.Context(), in.Host); err == nil { |
| 43 | t.Fatal("successor inherited placement") |
| 44 | } |
| 45 | b.state.Write(in.Host+".json", []byte("broken")) |
| 46 | if _, _, err := d.Placement(in.Host); err == nil { |
| 47 | t.Fatal("corruption fell through") |
| 48 | } |
| 49 | d.shapes = func() (map[string]claimUnitShape, error) { return nil, os.ErrPermission } |
| 50 | if _, _, err := d.Placement("sibling.example"); err != nil { |
| 51 | t.Fatal("unreadable named configuration blocked ordinary cleanup", err) |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | func TestPlan14BackingUnitRegistryShape(t *testing.T) { |
| 56 | b, in, s := plan14UnitBindingFixture(t, "carlos-app@shop.example.service", "unit", "") |
| 57 | if _, err := b.establish(t.Context(), in.Host); err != nil { |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | r := s.route() |
| 61 | if r.Unit != "" { |
| 62 | t.Fatal("canonical backing route invents a named unit", r.Unit) |
| 63 | } |
| 64 | if _, err := b.check(t.Context(), in.Host, &r); err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | r.Unit = s.Unit |
| 68 | if _, err := b.check(t.Context(), in.Host, &r); err == nil { |
| 69 | t.Fatal("accepted backing-to-named shape change") |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | func TestPlan14UnitBindingRetainedOwnership(t *testing.T) { |
| 74 | b, in, shape := plan14UnitBindingFixture(t, "console.service", "", "") |
| 75 | if _, err := b.establish(t.Context(), in.Host); err != nil { |
| 76 | t.Fatal(err) |
| 77 | } |
| 78 | other := in |
| 79 | other.Host, other.Record = "sibling.example", "apps/bab/shop/instances/sibling.example.json" |
| 80 | shape.Host = other.Host |
| 81 | b.installation = func(context.Context, string) (hostguard.RootInstallation, error) { return other, nil } |
| 82 | b.canonical = func(context.Context, string) (claimUnitShape, error) { return shape, nil } |
| 83 | if _, err := b.establish(t.Context(), other.Host); err == nil { |
| 84 | t.Fatal("root config reassigned a retained unit to a sibling") |
| 85 | } |
| 86 | if _, err := b.read(other.Host); !os.IsNotExist(err) { |
| 87 | t.Fatal("created conflicting owner", err) |
| 88 | } |
| 89 | } |
| 90 | |