mesh/delivery merged: source.main moved to novox/mesh-delivery@a3a0d8f2ceb3
mesh/check checking 5fe8ab11f1b4 on a3a0d8f2ceb3
122 lines
5.3 KiB
Go
122 lines
5.3 KiB
Go
package assign
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Until ADR 0547's third milestone two sources name which module runs on which node: the controller's assignments and
|
|
// the seat mesh-placement's placement (ADR 0549 §5, ADR 0567 §8). The assigner writes wants only for a pair both name:
|
|
// for a pair one source names alone it writes no want and touches none it wrote before, so nothing moves and nothing is
|
|
// removed on one source's word, and it raises a condition naming the pair until both agree. After an approved proposal
|
|
// the assigner keeps the controller in step itself, calling its assign, unassign, pin and unpin; a call refused or not
|
|
// yet answered leaves the pair differing, and the condition names the call (Pair.Call). Before the first placement key
|
|
// is written the controller's assignments are the one source, as before.
|
|
|
|
// Pair is a module on a node that one source names and the other does not; Only says which: OnlyAssignments or
|
|
// OnlyPlacement.
|
|
type Pair struct {
|
|
Node, Module, Only string
|
|
// Call is what became of the assigner's call of the controller's verb for the pair, in words, when it made one.
|
|
Call string
|
|
}
|
|
|
|
// The source that names a pair alone.
|
|
const (
|
|
OnlyAssignments = "the controller's assignments"
|
|
OnlyPlacement = "the seat mesh-placement's placement"
|
|
)
|
|
|
|
// Placed is, by node, the modules both sources name, in name order, and every pair one names alone, by node then
|
|
// module. With no placement read yet (seeded false) it is the assignments, and no pair differs.
|
|
func Placed(assigned, placed map[string][]string, seeded bool) (map[string][]string, []Pair) {
|
|
if !seeded {
|
|
return assigned, nil
|
|
}
|
|
set := func(m map[string][]string) map[string]bool {
|
|
out := map[string]bool{}
|
|
for node, modules := range m {
|
|
for _, module := range modules {
|
|
out[node+"\x00"+module] = true
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
inAssigned, inPlaced := set(assigned), set(placed)
|
|
both := map[string][]string{}
|
|
var differ []Pair
|
|
for node, modules := range assigned {
|
|
for _, module := range modules {
|
|
if inPlaced[node+"\x00"+module] {
|
|
both[node] = append(both[node], module)
|
|
} else {
|
|
differ = append(differ, Pair{Node: node, Module: module, Only: OnlyAssignments})
|
|
}
|
|
}
|
|
}
|
|
for node, modules := range placed {
|
|
for _, module := range modules {
|
|
if !inAssigned[node+"\x00"+module] {
|
|
differ = append(differ, Pair{Node: node, Module: module, Only: OnlyPlacement})
|
|
}
|
|
}
|
|
}
|
|
for _, modules := range both {
|
|
sort.Strings(modules)
|
|
}
|
|
sort.Slice(differ, func(i, j int) bool {
|
|
if differ[i].Node != differ[j].Node {
|
|
return differ[i].Node < differ[j].Node
|
|
}
|
|
return differ[i].Module < differ[j].Module
|
|
})
|
|
return both, differ
|
|
}
|
|
|
|
// PlacementCondition is the condition of a pair the two sources do not both name: raised while they differ, cleared once
|
|
// both name it or neither does.
|
|
func PlacementCondition(p Pair, raised bool) asks.Condition {
|
|
key := delivery.AssignerModule + ".placement." + p.Node + "." + p.Module
|
|
subject := asks.Scope{Scope: "module", ID: p.Module, Machine: p.Node}
|
|
if !raised {
|
|
return asks.Condition{Key: key, Subject: subject, State: asks.StateCleared,
|
|
Resolved: fmt.Sprintf("the controller's assignments and the seat's placement agree on %s on %s.", p.Module, p.Node)}
|
|
}
|
|
headline := fmt.Sprintf("%s on %s is placed by one source only", p.Module, p.Node)
|
|
if n := len([]rune(headline)); n > asks.HeadlineLength {
|
|
headline = string([]rune(headline)[:asks.HeadlineLength])
|
|
}
|
|
var needs string
|
|
if p.Only == OnlyAssignments {
|
|
needs = fmt.Sprintf("propose placing %s on %s (mesh-placement.propose), or unassign it at the controller", p.Module, p.Node)
|
|
} else {
|
|
needs = fmt.Sprintf("assign %s to %s at the controller, as the approved placement says, or propose taking it off", p.Module, p.Node)
|
|
}
|
|
if p.Call != "" {
|
|
needs += "; the assigner's call: " + p.Call
|
|
}
|
|
return asks.Condition{Key: key, Severity: asks.SeverityUrgent, Subject: subject, State: asks.StateRaised,
|
|
Summary: headline, Headline: headline, Needs: needs,
|
|
Explanation: fmt.Sprintf("%s%s. %s names %s on %s and the other source does not. Until ADR 0547's third milestone both must "+
|
|
"name a module on a node (ADR 0549 §5): the assigner writes no want for it there and leaves the one it wrote before, so "+
|
|
"nothing moves or is removed. This clears once both name it or neither does.", asks.NeedsYou, needs, p.Only, p.Module, p.Node)}
|
|
}
|
|
|
|
// SeatWaitsCondition is the assigner's word on the seat mesh-placement while a state of the seat is not bound (ADR 0567
|
|
// §1): raised naming each, cleared when none is left.
|
|
func SeatWaitsCondition(unbound []string) asks.Condition {
|
|
key := delivery.AssignerModule + ".seat-states"
|
|
subject := asks.Scope{Scope: "module", ID: delivery.AssignerModule}
|
|
if len(unbound) == 0 {
|
|
return asks.Condition{Key: key, Subject: subject, State: asks.StateCleared, Resolved: "the seat mesh-placement holds all its states."}
|
|
}
|
|
why := "the seat waits for its states: " + strings.Join(unbound, ", ") + " are not on the bus or not granted yet"
|
|
return asks.Condition{Key: key, Severity: asks.SeverityWarning, Subject: subject, State: asks.StateRaised,
|
|
Summary: "the seat waits for its states", Headline: "the seat waits for its states",
|
|
Explanation: asks.NothingToDo + " " + why + "; each is bound at the next pass once it is, with no restart."}
|
|
}
|