zdd-readiness: final review round 1, one definition of the claim containment name
Merge item 8: internal/cgroup's retaining setup recognised claim containment groups by a hand copy of internal/host's claim-<hex>.local format, and it runs flag off. The format now lives in internal/cgroup (ClaimContainmentName, IsClaimContainment, platform-neutral); internal/host builds its names with it, and a host test pins that every name it makes reads as a claim containment there, as a host name and as its exec cgroup's name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 files changed,
+64
−11
internal/cgroup/cgroup.go+26 −0internal/cgroup/recreate_linux.go+2 −10internal/host/claimcontainment_name_test.go+35 −0internal/host/claimroleassembly.go+1 −1
diff --git a/internal/cgroup/cgroup.go b/internal/cgroup/cgroup.go| index 2266b79e5..55fad4bb8 100644 |
| --- a/internal/cgroup/cgroup.go |
| +++ b/internal/cgroup/cgroup.go |
| @@ -43,6 +43,32 @@ func IsExecName(name string) bool { |
| return strings.HasPrefix(name, execPrefix) && len(name) > len(execPrefix) |
| } |
| +// The claim containment groups' host names: claim-<id>.local, where id is |
| +// the hex digest internal/host derives from the claim's fence target. This |
| +// is the ONE definition of the format (final review round 1, merge item 8): |
| +// internal/host builds names with ClaimContainmentName, and the retaining |
| +// setup (SetupRetaining), which runs flag off too, recognises them with |
| +// IsClaimContainment, so the two cannot drift apart. |
| +const ( |
| + claimContainmentPrefix = "claim-" |
| + claimContainmentSuffix = ".local" |
| +) |
| + |
| +// ClaimContainmentName is the containment group host name for a claim whose |
| +// identity digest is id (hex). |
| +func ClaimContainmentName(id string) string { |
| + return claimContainmentPrefix + id + claimContainmentSuffix |
| +} |
| + |
| +// IsClaimContainment reports whether name is a claim containment group's: |
| +// its host name (ClaimContainmentName) or its exec cgroup's directory name |
| +// (ExecName of one). |
| +func IsClaimContainment(name string) bool { |
| + host := strings.TrimPrefix(name, execPrefix) |
| + return strings.HasPrefix(host, claimContainmentPrefix) && strings.HasSuffix(host, claimContainmentSuffix) && |
| + len(host) > len(claimContainmentPrefix)+len(claimContainmentSuffix) |
| +} |
| + |
| // Limit is what Start writes into a host's cgroup before the child lands |
| // in it. MemoryMax is bytes; zero means "write nothing" — the pre-spec |
| // behaviour for a caller with no tier. memory.high is derived (90% of |
diff --git a/internal/cgroup/recreate_linux.go b/internal/cgroup/recreate_linux.go| index affd524d5..ec730ff40 100644 |
| --- a/internal/cgroup/recreate_linux.go |
| +++ b/internal/cgroup/recreate_linux.go |
| @@ -236,7 +236,7 @@ func refuseKilledChild(parent *Prepared, name string) error { |
| // the first start into it makes it fresh. Being marked costs an empty |
| // cgroup only a recreation. A directory that holds a process, or whose |
| // population cannot be read, is never marked: removing it is not ours to |
| -// do. The claim containment groups (carlos-exec@claim-<id>.local, unique |
| +// do. The claim containment groups (IsClaimContainment: carlos-exec@claim-<id>.local, unique |
| // to one controller) are left alone: they are the retained evidence the |
| // retaining mode exists to keep, and nothing starts into them again. |
| func (m *Manager) seedKilled() { |
| @@ -246,7 +246,7 @@ func (m *Manager) seedKilled() { |
| } |
| seeded := 0 |
| for _, e := range entries { |
| - if !e.IsDir() || !IsExecName(e.Name()) || isClaimContainment(e.Name()) { |
| + if !e.IsDir() || !IsExecName(e.Name()) || IsClaimContainment(e.Name()) { |
| continue |
| } |
| dir := filepath.Join(m.root, e.Name()) |
| @@ -295,11 +295,3 @@ func seedIfEmpty(dir string) bool { |
| markKilled(id) |
| return true |
| } |
| - |
| -// isClaimContainment reports whether an exec cgroup name is a claim |
| -// containment group's: carlos-exec@claim-<hex>.local, as |
| -// internal/host's claimContainmentName makes them. |
| -func isClaimContainment(name string) bool { |
| - host := strings.TrimPrefix(name, execPrefix) |
| - return strings.HasPrefix(host, "claim-") && strings.HasSuffix(host, ".local") |
| -} |
diff --git a/internal/host/claimcontainment_name_test.go b/internal/host/claimcontainment_name_test.go| new file mode 100644 |
| index 000000000..4c498bc60 |
| --- /dev/null |
| +++ b/internal/host/claimcontainment_name_test.go |
| @@ -0,0 +1,35 @@ |
| +package host |
| + |
| +import ( |
| + "testing" |
| + |
| + "github.com/carlosframework/platform/internal/cgroup" |
| + "github.com/carlosframework/platform/internal/hostguard" |
| +) |
| + |
| +// Final review round 1, merge item 8: internal/cgroup's retaining setup |
| +// leaves claim containment groups alone (it seeds and sweeps around them, |
| +// flag off included), and it knows them only by their name. The name's |
| +// format has one definition, in internal/cgroup, and every name this |
| +// package makes must read as one there, both as the containment's host name |
| +// and as its exec cgroup's directory name. A plain host never does. |
| +func TestClaimContainmentNamesAreRecognisedByTheCgroupPackage(t *testing.T) { |
| + for _, target := range []hostguard.FenceTarget{ |
| + {}, |
| + {Host: "a.example.com", Incarnation: "inc-1", Stamp: 3, Session: 9, Box: "box-1", Enrollment: "e", BootID: "boot", ControllerID: "ctl"}, |
| + {Host: "claim-x.local", Incarnation: "inc-2", Stamp: 1, Session: 1, Box: "box-2"}, |
| + } { |
| + name := claimContainmentName(target) |
| + if !cgroup.IsClaimContainment(name) { |
| + t.Fatalf("cgroup.IsClaimContainment(%q) = false for a claim containment name", name) |
| + } |
| + if !cgroup.IsClaimContainment(cgroup.ExecName(name)) { |
| + t.Fatalf("cgroup.IsClaimContainment(%q) = false for its exec cgroup", cgroup.ExecName(name)) |
| + } |
| + } |
| + for _, plain := range []string{"a.example.com", cgroup.ExecName("a.example.com"), "claim-a.example.com", "x.local"} { |
| + if cgroup.IsClaimContainment(plain) { |
| + t.Fatalf("cgroup.IsClaimContainment(%q) = true for a name that is not a claim containment's", plain) |
| + } |
| + } |
| +} |
diff --git a/internal/host/claimroleassembly.go b/internal/host/claimroleassembly.go| index 5946bcc55..b5764512e 100644 |
| --- a/internal/host/claimroleassembly.go |
| +++ b/internal/host/claimroleassembly.go |
| @@ -38,7 +38,7 @@ func claimContainmentName(target hostguard.FenceTarget) string { |
| }{target.Host, target.Incarnation, target.Stamp, target.Session, target.Box, target.Enrollment, target.BootID, target.ControllerID} |
| raw, _ := json.Marshal(identity) |
| sum := sha256.Sum256(raw) |
| - return "claim-" + hex.EncodeToString(sum[:24]) + ".local" |
| + return cgroup.ClaimContainmentName(hex.EncodeToString(sum[:24])) // the one format (merge item 8) |
| } |
| // launchTarget binds the observed session to this controller's verified tuple. |