The controller ended by its unassignment (ADR 0590) and mesh-sdk drops its seat, the grants seat and placement's adopt and hand-back. The indexer's judge still carried the controller's seat entry, the old removal words and the two placement verbs, and counted 58 seats; the checker still placed mesh-controller beside the node-engine's and the catalogue's tests and failed a node-engine check without its trunk; the assigner still asked a compose of the running controller, served adopt and hand-back as stubs, and kept a policy-split finding that could no longer arise. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
136 lines
6.3 KiB
Go
136 lines
6.3 KiB
Go
package assign
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/health"
|
|
"git.novox.be/novox/mesh-sdk/go/provisions"
|
|
)
|
|
|
|
// A person's upgrade policy is the assigner's (novox/hq ADR 0565 §7, §8, issue 1953 D4, D5): kept in its state `policies`, one
|
|
// key per module, set and taken back through `mesh-wants.upgrade`. Every reader takes one derivation, mesh-sdk's
|
|
// catalogue.Upgrade: a person's choice over the module's word, its irreplaceable data and the default, the bus never rolled.
|
|
|
|
// PoliciesBucket is the assigner's state `policies`.
|
|
var PoliciesBucket = delivery.AssignerModule + "_" + delivery.AssignerPoliciesState
|
|
|
|
// DecodePolicies reads the policies state's values by key (the module); a value that cannot be read is left out, named.
|
|
func DecodePolicies(values map[string][]byte) (map[string]catalogue.PersonPolicy, []string) {
|
|
out := map[string]catalogue.PersonPolicy{}
|
|
var bad []string
|
|
for key, raw := range values {
|
|
var p catalogue.PersonPolicy
|
|
if err := json.Unmarshal(raw, &p); err != nil || p.V != catalogue.PersonPolicyVersion || p.Check() != nil || p.Module != key {
|
|
bad = append(bad, key)
|
|
continue
|
|
}
|
|
out[key] = p
|
|
}
|
|
sort.Strings(bad)
|
|
return out, bad
|
|
}
|
|
|
|
// PolicyOf is a module's policy and where it comes from, with the person's choice for it when there is one.
|
|
func PolicyOf(m catalogue.Manifest, chosen map[string]catalogue.PersonPolicy) (policy, from, why, by string) {
|
|
var c *catalogue.PersonPolicy
|
|
if p, ok := chosen[m.Module]; ok {
|
|
c = &p
|
|
}
|
|
return catalogue.Upgrade(m, c)
|
|
}
|
|
|
|
func contains(list []string, s string) bool {
|
|
for _, v := range list {
|
|
if v == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// KindProvisionFailed is the assigner's finding of a provision its module's want waits for that failed (ADR 0570 §4).
|
|
const KindProvisionFailed = "provision-failed"
|
|
|
|
// ProvisionFailedKey is a failed provision's finding's key, `provider.<node>.<consumer>.<provision>[.<local>].provision-failed`.
|
|
func ProvisionFailedKey(key string) string {
|
|
return health.FindingKey(health.ScopeProvider, key, KindProvisionFailed)
|
|
}
|
|
|
|
// ProvisionFailedFinding is the finding of a failed provision, naming its consumer, its provider and why.
|
|
func ProvisionFailedFinding(key string, p provisions.Provision, at time.Time) ([]byte, error) {
|
|
summary := fmt.Sprintf("%s for %s on %s failed at %s on %s: %s, so %s is not moved there until it is provided", p.Provision,
|
|
p.Consumer.Module, p.Consumer.Node, p.Provider.Module, p.Provider.Node, p.Why, p.Consumer.Module)
|
|
return health.EncodeFinding(health.Finding{Key: ProvisionFailedKey(key), Kind: KindProvisionFailed,
|
|
Subject: health.Subject{Scope: health.ScopeProvider, ID: key, Machine: p.Consumer.Node, Also: also(p.Provider.Node, p.Consumer.Node)},
|
|
Severity: health.Warning, Summary: summary, Headline: clipHeadline(p.Consumer.Module + " waits for a provision that failed"),
|
|
Explanation: asks.NothingToDo + " " + summary + ". It clears once the provider provides it.",
|
|
Resolved: p.Provision + " for " + p.Consumer.Module + " is provided", Evidence: []health.Evidence{{At: at.UTC(), Said: p.Why}},
|
|
Source: "assigner." + KindProvisionFailed, Observer: delivery.AssignerModule, At: at.UTC()})
|
|
}
|
|
|
|
func also(provider, consumer string) []string {
|
|
if provider == "" || provider == consumer {
|
|
return nil
|
|
}
|
|
return []string{provider}
|
|
}
|
|
|
|
// KindProvisionMissing is the assigner's finding of a required provision that has had no key for longer than its bound.
|
|
const KindProvisionMissing = "provision-missing"
|
|
|
|
// ProvisionMissingKey is its key, `provider.<node>.<module>.<provision>.provision-missing`.
|
|
func ProvisionMissingKey(key string) string {
|
|
return health.FindingKey(health.ScopeProvider, key, KindProvisionMissing)
|
|
}
|
|
|
|
// ProvisionMissingFinding is the finding of a required provision with no key past bound (key `<node>.<module>.<provision>`).
|
|
func ProvisionMissingFinding(key string, bound time.Duration, at time.Time) ([]byte, error) {
|
|
parts := strings.SplitN(key, ".", 3)
|
|
if len(parts) != 3 {
|
|
return nil, fmt.Errorf("%q is not <node>.<module>.<provision>", key)
|
|
}
|
|
node, module, provision := parts[0], parts[1], parts[2]
|
|
summary := fmt.Sprintf("%s on %s requires %s and mesh-provisioner has composed no key for it in %s, so %s is not moved there: "+
|
|
"no provider of it may be placed", module, node, provision, bound, module)
|
|
return health.EncodeFinding(health.Finding{Key: ProvisionMissingKey(key), Kind: KindProvisionMissing,
|
|
Subject: health.Subject{Scope: health.ScopeProvider, ID: key, Machine: node},
|
|
Severity: health.Warning, Summary: summary, Headline: clipHeadline(module + " on " + node + " lacks " + provision),
|
|
Explanation: asks.NothingToDo + " " + summary + ". It clears once the provision has its key.",
|
|
Resolved: provision + " for " + module + " on " + node + " has its key", Evidence: []health.Evidence{{At: at.UTC(), Said: summary}},
|
|
Source: "assigner." + KindProvisionMissing, Observer: delivery.AssignerModule, At: at.UTC()})
|
|
}
|
|
|
|
// KindUnplaced is, from the lease write, a node that runs but no placement names (ADR 0571 §6).
|
|
const KindUnplaced = "unplaced"
|
|
|
|
// UnplacedKey is its finding's key, `machine.<node>.unplaced`.
|
|
func UnplacedKey(node string) string {
|
|
return health.FindingKey(health.ScopeMachine, node, KindUnplaced)
|
|
}
|
|
|
|
// UnplacedFinding is the finding of a node no placement names since the lease write.
|
|
func UnplacedFinding(node string, at time.Time) ([]byte, error) {
|
|
summary := fmt.Sprintf("%s runs, but no placement names it: since the controller handed its lease away nothing composes it, "+
|
|
"so the assigner writes it no want", node)
|
|
return health.EncodeFinding(health.Finding{Key: UnplacedKey(node), Kind: KindUnplaced,
|
|
Subject: health.Subject{Scope: health.ScopeMachine, ID: node, Machine: node}, Severity: health.Warning, Summary: summary,
|
|
Headline: clipHeadline(node + " has no placement"), Needs: "place it with mesh-placement.propose.",
|
|
Explanation: asks.NeedsYou + "place it with mesh-placement.propose. " + summary + ".",
|
|
Resolved: node + " is placed", Evidence: []health.Evidence{{At: at.UTC(), Said: summary}},
|
|
Source: "assigner." + KindUnplaced, Observer: delivery.AssignerModule, At: at.UTC()})
|
|
}
|
|
|
|
func clipHeadline(s string) string {
|
|
if len([]rune(s)) <= 60 {
|
|
return s
|
|
}
|
|
return string([]rune(s)[:60])
|
|
}
|