The names region attributed a composed name to whichever labelled contribution a map yielded last. A dashboard contributes its label to its route and to the identity provider, which must know the public name for a redirect; so on one plan grafana.<domain> pointed at the proxy's machine and on the next at the identity provider's, and the whole region flipped with it (forge issue 227). And a module routed several times contributed no name at all, because the single-value reading of its contributions is empty for the many shape. Now every node's resolution is read first and the names are attributed across them at once: the terminus serves the name — the provider that is not itself published under a labelled name through another — walked in order, so one mesh yields one region. Name-agnostic, structural, deterministic.
125 lines
4.2 KiB
Go
125 lines
4.2 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Which machine serves each routed name (novox/hq ADR 0066, issue 178).
|
|
//
|
|
// A routed name is a label the mesh composed for a consumer's endpoint, and it is *served* by the
|
|
// provider that answers requests for it — the proxy the consumer's route reaches. The same name is
|
|
// composed into every labelled contribution the consumer makes, because a provider that must know
|
|
// the consumer's public name (an identity provider composing a redirect) is told it the same way
|
|
// (04-ISSUES/122). Attributing the name to whichever of those providers a map happened to yield
|
|
// last sent a public name to the identity provider's machine on one plan and to the proxy's on the
|
|
// next (forge issue 227), and the whole names region flipped with it.
|
|
//
|
|
// **The terminus serves the name.** Among the providers a name reaches, the one that serves it is
|
|
// the one that is not itself routed: a provider that contributes a labelled name of its own to some
|
|
// requirement is published through another provider, and is a consumer of names, not their end.
|
|
// Name-agnostic — nothing here knows what "route" means — and structural: it reads the graph the
|
|
// modules declared. Deterministic: names, requirements and nodes are walked in order, so two
|
|
// plans of one mesh yield one region.
|
|
|
|
// NamesServed is every routed name across the mesh and the node that serves it, from every node's
|
|
// resolution and settings. A name several termini claim goes to the first node in name order, so
|
|
// the answer is stable; a name nothing terminal claims is left out.
|
|
func NamesServed(plans map[string]Resolution, settings map[string]SettingsBy) (map[string]string, error) {
|
|
nodes := make([]string, 0, len(plans))
|
|
for n := range plans {
|
|
nodes = append(nodes, n)
|
|
}
|
|
sort.Strings(nodes)
|
|
|
|
out := map[string]string{}
|
|
for _, node := range nodes {
|
|
plan := plans[node]
|
|
all, err := plan.contributions(settings[node], nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
requirements := make([]string, 0, len(all))
|
|
for to := range all {
|
|
requirements = append(requirements, to)
|
|
}
|
|
sort.Strings(requirements)
|
|
for _, to := range requirements {
|
|
for _, given := range all[to] {
|
|
if given.Node != "" {
|
|
// Said from another machine; that machine's own resolution carries it.
|
|
continue
|
|
}
|
|
// A routed name, and only that: a contribution the mesh composed a name for from a
|
|
// label it was given. A grant that happens to carry a `name` of its own — a database
|
|
// name — carries no label and is left alone.
|
|
if _, labelled := given.Values["label"]; !labelled {
|
|
continue
|
|
}
|
|
name, _ := given.Values["name"].(string)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
serving := servingNodeOf(plan, to, given.From, node)
|
|
if !servesNames(plans[serving], to) {
|
|
continue
|
|
}
|
|
name = strings.ToLower(name)
|
|
if held, taken := out[name]; !taken || serving < held {
|
|
out[name] = serving
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// servingNodeOf is the node answering one consumer's requirement: whoever the plan needs it from,
|
|
// or this same node when the provider is beside the consumer.
|
|
func servingNodeOf(plan Resolution, requirement, consumer, self string) string {
|
|
for _, need := range plan.Needs {
|
|
if need.Name == requirement && need.For == consumer && need.From != "" {
|
|
return need.From
|
|
}
|
|
}
|
|
return self
|
|
}
|
|
|
|
// servesNames says whether the module providing a requirement on a node is a terminus: it is not
|
|
// itself published under a labelled name through some other provider. A node whose plan is not
|
|
// known (it did not resolve) serves nothing.
|
|
func servesNames(plan Resolution, requirement string) bool {
|
|
for _, m := range plan.Modules {
|
|
if !offers(m, requirement) {
|
|
continue
|
|
}
|
|
return !contributesALabel(m)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func offers(m Manifest, requirement string) bool {
|
|
for _, o := range m.Offers() {
|
|
if o == requirement {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func contributesALabel(m Manifest) bool {
|
|
for _, values := range m.Contributes {
|
|
if _, labelled := values["label"]; labelled {
|
|
return true
|
|
}
|
|
}
|
|
for _, locals := range m.ContributesMany {
|
|
for _, values := range locals {
|
|
if _, labelled := values["label"]; labelled {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|