Name the account whose own manager runs a unit, so a wait for a new login is not read as a fault (hq ADR 0254, issue 318)
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/318-a-wait-for-a-person-is-not-a-failure delivered: every member is delivered
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/318-a-wait-for-a-person-is-not-a-failure delivered: every member is delivered
This commit is contained in:
+12
-3
@@ -1565,7 +1565,7 @@ func healthAsReported(st liveness.Statement, ns *network.Statement) *link.Health
|
|||||||
for _, r := range st.Resources {
|
for _, r := range st.Resources {
|
||||||
h.Resources = append(h.Resources, link.ResourceHealth{Module: r.Module, Resource: r.ID, Kind: r.Kind,
|
h.Resources = append(h.Resources, link.ResourceHealth{Module: r.Module, Resource: r.ID, Kind: r.Kind,
|
||||||
Target: r.Target, State: r.State, Reason: r.Reason, Since: r.Since.UTC(), Streak: r.Streak,
|
Target: r.Target, State: r.State, Reason: r.Reason, Since: r.Since.UTC(), Streak: r.Streak,
|
||||||
Restarts: r.Restarts, Check: r.CheckOf(), Needs: r.NeedsOf()})
|
Restarts: r.Restarts, Check: r.CheckOf(), Needs: r.NeedsOf(), Account: userOf(r.Scope, r.User)})
|
||||||
}
|
}
|
||||||
if ns != nil && ns.State != "" {
|
if ns != nil && ns.State != "" {
|
||||||
h.Network = &link.NetworkHealth{State: ns.State, Since: ns.Since.UTC(), Parts: []link.NetworkPart{}}
|
h.Network = &link.NetworkHealth{State: ns.State, Since: ns.Since.UTC(), Parts: []link.NetworkPart{}}
|
||||||
@@ -1600,7 +1600,7 @@ func withUnits(h *link.Health, us *units.Statement) *link.Health {
|
|||||||
}
|
}
|
||||||
h.Resources = append(h.Resources, link.ResourceHealth{Module: f.Module, Resource: f.Resource,
|
h.Resources = append(h.Resources, link.ResourceHealth{Module: f.Module, Resource: f.Resource,
|
||||||
Kind: link.KindUnit, Target: f.Unit, State: link.StateUnhealthy, Reason: reason, Since: f.Since.UTC(),
|
Kind: link.KindUnit, Target: f.Unit, State: link.StateUnhealthy, Reason: reason, Since: f.Since.UTC(),
|
||||||
Streak: f.Streak})
|
Streak: f.Streak, Account: userOf(f.Scope, f.User)})
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
@@ -1615,11 +1615,20 @@ func withAccounts(h *link.Health, as *accounts.Statement) *link.Health {
|
|||||||
for _, v := range as.Accounts {
|
for _, v := range as.Accounts {
|
||||||
h.Resources = append(h.Resources, link.ResourceHealth{Module: v.Module, Resource: v.ID,
|
h.Resources = append(h.Resources, link.ResourceHealth{Module: v.Module, Resource: v.ID,
|
||||||
Kind: link.KindAccount, Target: v.Name, State: v.State, Reason: v.Reason, Since: v.Since.UTC(),
|
Kind: link.KindAccount, Target: v.Name, State: v.State, Reason: v.Reason, Since: v.Since.UTC(),
|
||||||
Streak: v.Streak})
|
Streak: v.Streak, Account: v.Name})
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// userOf is the account whose own service manager a unit is in (novox/hq ADR 0254), empty for the
|
||||||
|
// machine's own manager.
|
||||||
|
func userOf(scope, user string) string {
|
||||||
|
if scope == declaration.ScopeUser {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// failedUnitWords is a failed unit as the console says it.
|
// failedUnitWords is a failed unit as the console says it.
|
||||||
func failedUnitWords(f units.Failed) string {
|
func failedUnitWords(f units.Failed) string {
|
||||||
whose := "no module places it"
|
whose := "no module places it"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/novox/mesh-host/internal/accounts"
|
||||||
|
"github.com/novox/mesh-host/internal/declaration"
|
||||||
"github.com/novox/mesh-host/internal/link"
|
"github.com/novox/mesh-host/internal/link"
|
||||||
"github.com/novox/mesh-host/internal/liveness"
|
"github.com/novox/mesh-host/internal/liveness"
|
||||||
"github.com/novox/mesh-host/internal/units"
|
"github.com/novox/mesh-host/internal/units"
|
||||||
@@ -17,7 +19,7 @@ func TestTheStatementCarriesTheFailedUnits(t *testing.T) {
|
|||||||
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
||||||
us := &units.Statement{At: at, State: units.Degraded, Failed: []units.Failed{
|
us := &units.Statement{At: at, State: units.Degraded, Failed: []units.Failed{
|
||||||
{Unit: "openrazer-daemon.service", Scope: units.ScopeUser, Load: "loaded", Result: "exit-code",
|
{Unit: "openrazer-daemon.service", Scope: units.ScopeUser, Load: "loaded", Result: "exit-code",
|
||||||
Module: "openrazer", Resource: "openrazer.daemon", Via: units.ViaPackage, Since: at, Streak: 2},
|
User: "operator", Module: "openrazer", Resource: "openrazer.daemon", Via: units.ViaPackage, Since: at, Streak: 2},
|
||||||
{Unit: "storage-media.mount", Scope: units.ScopeSystem, Load: "loaded", Result: "timeout", Since: at, Streak: 2},
|
{Unit: "storage-media.mount", Scope: units.ScopeSystem, Load: "loaded", Result: "timeout", Since: at, Streak: 2},
|
||||||
}}
|
}}
|
||||||
h := withUnits(healthAsReported(liveness.Statement{At: at}, nil), us)
|
h := withUnits(healthAsReported(liveness.Statement{At: at}, nil), us)
|
||||||
@@ -30,6 +32,10 @@ func TestTheStatementCarriesTheFailedUnits(t *testing.T) {
|
|||||||
!strings.Contains(r.Reason, "account's own service manager") || !strings.Contains(r.Reason, "exit-code") {
|
!strings.Contains(r.Reason, "account's own service manager") || !strings.Contains(r.Reason, "exit-code") {
|
||||||
t.Fatalf("the module's failed unit: %+v", r)
|
t.Fatalf("the module's failed unit: %+v", r)
|
||||||
}
|
}
|
||||||
|
// Whose manager it fails in, so the controller can tell it from a fault (novox/hq ADR 0254).
|
||||||
|
if r.Account != "operator" {
|
||||||
|
t.Fatalf("the unit in the account's own manager does not name the account: %q", r.Account)
|
||||||
|
}
|
||||||
if h.Units == nil || h.Units.State != units.Degraded || len(h.Units.Failed) != 1 ||
|
if h.Units == nil || h.Units.State != units.Degraded || len(h.Units.Failed) != 1 ||
|
||||||
h.Units.Failed[0].Unit != "storage-media.mount" || h.Units.Failed[0].Result != "timeout" {
|
h.Units.Failed[0].Unit != "storage-media.mount" || h.Units.Failed[0].Result != "timeout" {
|
||||||
t.Fatalf("the machine's own: %+v", h.Units)
|
t.Fatalf("the machine's own: %+v", h.Units)
|
||||||
@@ -41,3 +47,31 @@ func TestTheStatementCarriesTheFailedUnits(t *testing.T) {
|
|||||||
t.Fatal("a judge not yet given a declaration said units")
|
t.Fatal("a judge not yet given a declaration said units")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Every resource that runs in an account's own service manager names the account, and so does the account
|
||||||
|
// a module put in a group (novox/hq ADR 0254): the controller reads a unit that cannot run before a new login
|
||||||
|
// by the two together. What the machine's own manager runs names none.
|
||||||
|
func TestTheStatementNamesTheAccountsManager(t *testing.T) {
|
||||||
|
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
||||||
|
st := liveness.Statement{At: at, Resources: []liveness.State{
|
||||||
|
{Resource: liveness.Resource{Module: "openrazer", ID: "openrazer.daemon", Kind: "service",
|
||||||
|
Target: "openrazer-daemon.service", Scope: declaration.ScopeUser, User: "operator"}, State: liveness.Unhealthy,
|
||||||
|
Reason: "down", Since: at},
|
||||||
|
{Resource: liveness.Resource{Module: "docker", ID: "docker.engine", Kind: "service", Target: "docker.service",
|
||||||
|
Scope: declaration.ScopeSystem}, State: liveness.Healthy, Since: at},
|
||||||
|
}}
|
||||||
|
as := &accounts.Statement{At: at, Accounts: []accounts.Verdict{{Account: accounts.Account{Module: "openrazer",
|
||||||
|
ID: "openrazer.operator", Name: "operator", Groups: []string{"openrazer"}}, State: accounts.Unhealthy,
|
||||||
|
Reason: accounts.ReasonRelogin + ": operator is in the group openrazer", Since: at}}}
|
||||||
|
h := withAccounts(healthAsReported(st, nil), as)
|
||||||
|
got := map[string]string{}
|
||||||
|
for _, r := range h.Resources {
|
||||||
|
got[r.Resource] = r.Account
|
||||||
|
}
|
||||||
|
want := map[string]string{"openrazer.daemon": "operator", "docker.engine": "", "openrazer.operator": "operator"}
|
||||||
|
for id, account := range want {
|
||||||
|
if a, said := got[id]; !said || a != account {
|
||||||
|
t.Errorf("%s names the account %q (said %v); want %q", id, a, said, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -350,6 +350,11 @@ type ResourceHealth struct {
|
|||||||
// provision it exercises, for the controller to hold what it finds under the provider (to-be 48 §6).
|
// provision it exercises, for the controller to hold what it finds under the provider (to-be 48 §6).
|
||||||
Check string `json:"check,omitempty"`
|
Check string `json:"check,omitempty"`
|
||||||
Needs string `json:"needs,omitempty"`
|
Needs string `json:"needs,omitempty"`
|
||||||
|
// Account is the account whose own service manager runs it, for a unit or a service in an account's
|
||||||
|
// manager, and the account itself for a resource of kind KindAccount (novox/hq ADR 0254): what lets the
|
||||||
|
// controller tell a unit that cannot run before a new login from one that is broken. Empty for anything
|
||||||
|
// the machine's own manager or a container runtime runs.
|
||||||
|
Account string `json:"account,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthSaid is the health event: a machine's statement between its reports, on HealthSubject.
|
// HealthSaid is the health event: a machine's statement between its reports, on HealthSubject.
|
||||||
|
|||||||
Reference in New Issue
Block a user