Raise a module's unhealthy urgent when its manifest says why (hq ADR 0355, issue 691)
mesh/merge-gate head 58889235d163 merged into trunk 535c7641ac41 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 58889235d163 merged into trunk 535c7641ac41 — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/merge-gate head 58889235d163 merged into trunk 535c7641ac41 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 58889235d163 merged into trunk 535c7641ac41 — pass: its merge-check.sh passed
mesh/delivery delivered
A broken service people rely on, such as mail, was said only as a warning on the desktop. A module may now declare unhealthy urgent with what people lose; the module check refuses it without why or with another severity.
This commit is contained in:
@@ -552,6 +552,10 @@ type Manifest struct {
|
||||
// why. A person's choice through the `upgrade` verb stands over it; the bus records whatever it says.
|
||||
Upgrade *UpgradePolicy `json:"upgrade,omitempty"`
|
||||
|
||||
// Unhealthy is how loud this module's unhealthy condition is (novox/hq ADR 0355): a warning when unsaid;
|
||||
// `urgent`, with why, for a module whose failure costs people something they rely on.
|
||||
Unhealthy *UnhealthyPolicy `json:"unhealthy,omitempty"`
|
||||
|
||||
// Reads are other modules' state this module reads and watches, each `<module>.<name>`
|
||||
// (novox/hq ADR 0201). Read-only: only the owner's instances write.
|
||||
Reads []string `json:"reads,omitempty"`
|
||||
@@ -2326,6 +2330,7 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
||||
problems = append(problems, m.placementProblems()...)
|
||||
problems = append(problems, m.dataProblems()...)
|
||||
problems = append(problems, m.upgradeProblems()...)
|
||||
problems = append(problems, m.unhealthyProblems()...)
|
||||
|
||||
for i, r := range m.Resources {
|
||||
id, _ := r["id"].(string)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// How loud a module's unhealthy condition is (novox/hq ADR 0355, extending ADR 0240).
|
||||
//
|
||||
// **A module may declare its unhealthy urgent.** A module whose failure costs people something they rely on
|
||||
// (mail refused or stuck for everyone with a mailbox) says so with `unhealthy`, and its condition
|
||||
// `module.<module>.<machine>.unhealthy` is raised urgent instead of a warning. **Urgent needs a why**, as an
|
||||
// `upgrade` policy other than roll does: a person reading the catalogue sees what is lost while it is broken.
|
||||
|
||||
// The severities a module may declare for its unhealthy condition.
|
||||
const (
|
||||
// UnhealthyUrgent raises the module's unhealthy condition urgent, on every machine it runs on.
|
||||
UnhealthyUrgent = "urgent"
|
||||
// UnhealthyWarning is what a module's unhealthy condition is without the field, said out loud.
|
||||
UnhealthyWarning = "warning"
|
||||
)
|
||||
|
||||
// UnhealthyPolicy is what a module says about how loud its unhealthy condition is: `unhealthy` in its manifest
|
||||
// (novox/hq ADR 0355).
|
||||
type UnhealthyPolicy struct {
|
||||
Severity string `json:"severity"`
|
||||
// Why names what people lose while the module is broken. It is required: a module that says nothing of
|
||||
// why it is loud is refused.
|
||||
Why string `json:"why,omitempty"`
|
||||
}
|
||||
|
||||
// unhealthyProblems refuses an `unhealthy` without a why, and any severity but urgent or warning (novox/hq ADR
|
||||
// 0355 §2).
|
||||
func (m Manifest) unhealthyProblems() []string {
|
||||
if m.Unhealthy == nil {
|
||||
return nil
|
||||
}
|
||||
var problems []string
|
||||
switch m.Unhealthy.Severity {
|
||||
case UnhealthyUrgent, UnhealthyWarning:
|
||||
default:
|
||||
problems = append(problems, fmt.Sprintf("unhealthy is %q or %q, not %q", UnhealthyUrgent, UnhealthyWarning,
|
||||
m.Unhealthy.Severity))
|
||||
}
|
||||
if strings.TrimSpace(m.Unhealthy.Why) == "" {
|
||||
problems = append(problems, "unhealthy says why: a module that declares how loud its failure is names "+
|
||||
"what people lose while it is broken")
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
// UnhealthyIsUrgent is whether the module declares its unhealthy condition urgent (novox/hq ADR 0355 §1).
|
||||
func (m Manifest) UnhealthyIsUrgent() bool {
|
||||
return m.Unhealthy != nil && m.Unhealthy.Severity == UnhealthyUrgent
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A module may declare its unhealthy urgent, with why; the module check refuses it without why and with any
|
||||
// severity but urgent or warning (novox/hq ADR 0355).
|
||||
|
||||
func TestAnUnhealthyDeclaredUrgentIsUrgent(t *testing.T) {
|
||||
m, err := ParseManifest([]byte(`{"module":"mail","version":"1",` +
|
||||
`"unhealthy":{"severity":"urgent","why":"mail is refused or stuck for everyone with a mailbox"}}`))
|
||||
if err != nil {
|
||||
t.Fatalf("a manifest declaring its unhealthy urgent with why was refused: %v", err)
|
||||
}
|
||||
if !m.UnhealthyIsUrgent() {
|
||||
t.Errorf("a manifest declaring its unhealthy urgent reads as not urgent: %+v", m.Unhealthy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAManifestWithoutUnhealthyIsNotUrgent(t *testing.T) {
|
||||
m, err := ParseManifest([]byte(`{"module":"notes","version":"1"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.UnhealthyIsUrgent() {
|
||||
t.Errorf("a manifest saying nothing of its unhealthy reads as urgent")
|
||||
}
|
||||
warning := Manifest{Module: "notes", Unhealthy: &UnhealthyPolicy{Severity: UnhealthyWarning, Why: "notes wait"}}
|
||||
if warning.UnhealthyIsUrgent() || len(warning.unhealthyProblems()) != 0 {
|
||||
t.Errorf("a warning said with why reads as urgent or is refused: %v", warning.unhealthyProblems())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnUnhealthyWithoutWhyIsRefused(t *testing.T) {
|
||||
for _, why := range []string{"", " "} {
|
||||
if problems := (Manifest{Module: "mail", Unhealthy: &UnhealthyPolicy{Severity: UnhealthyUrgent, Why: why}}).unhealthyProblems(); len(problems) == 0 {
|
||||
t.Errorf("urgent with why %q was accepted", why)
|
||||
}
|
||||
}
|
||||
if _, err := ParseManifest([]byte(`{"module":"mail","version":"1","unhealthy":{"severity":"urgent"}}`)); err == nil ||
|
||||
!strings.Contains(err.Error(), "unhealthy says why") {
|
||||
t.Errorf("a manifest declaring its unhealthy urgent without why parsed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnUnhealthySeverityOtherThanUrgentOrWarningIsRefused(t *testing.T) {
|
||||
if _, err := ParseManifest([]byte(`{"module":"mail","version":"1","unhealthy":{"severity":"loud","why":"mail stops"}}`)); err == nil ||
|
||||
!strings.Contains(err.Error(), `not "loud"`) {
|
||||
t.Errorf("a manifest declaring its unhealthy loud parsed: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user