package resolution import ( "fmt" "slices" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) // keepBound holds every need for a provision that keeps its consumers' data at the provider its // consumer was bound to, where the resolution chose another. // // A need with no record is a binding being made, and is left as resolved: it is recorded when it is // first sent. A pin naming the provider the resolution chose is a person moving it, and is left too. // Otherwise the recorded provider answers, if it still provides the provision — beside the consumer, // or offered from elsewhere — and the move is returned as kept. **One that no longer does is refused, // never answered by the provider chosen**: answering it there is exactly the silent move this exists // to stop, and the consumer's data is still wherever it was. func keepBound(needs []mcat.Needed, catalogue map[string]mcat.Manifest, node mcat.Node, world mcat.World, keeps map[string]bool, here func(string) bool) ([]mcat.Needed, []mcat.KeptBinding, []string) { var kept []mcat.KeptBinding var problems []string refused := map[[2]string]bool{} out := make([]mcat.Needed, 0, len(needs)) for _, n := range needs { if n.ByRecord || !keeps[n.Name] { out = append(out, n) continue } n.KeepsData = true bound, recorded := world.Bound[n.For][n.Name] chose := mcat.Chosen{Node: n.From, Module: n.Module} if !recorded || sameProvider(bound, chose) { out = append(out, n) continue } if pin, pinned := world.Pinned[n.Name]; pinned && chosenMatches(pin, mcat.Provider{Node: chose.Node, Module: chose.Module}) { out = append(out, n) continue } held, ok, why := boundNeed(n, bound, catalogue, node, world, here) if !ok { if key := [2]string{n.For, n.Name}; !refused[key] { refused[key] = true problems = append(problems, fmt.Sprintf( "%s on %s is bound to %s for %q, which keeps its data, and %s — it would move to %s, "+ "which holds none of it. Move its data and say so with `pin %s %s %s %s`, or give "+ "%s back what it provided", n.For, node.Name, bound, n.Name, why, chose, node.Name, n.Name, chose.Node, chose.Module, bound.Node)) } continue } out = append(out, held) kept = append(kept, mcat.KeptBinding{Machine: node.Name, Consumer: n.For, Provision: n.Name, Bound: bound, Would: chose}) } return out, kept, problems } // sameProvider is whether a recorded provider is the one chosen. A record naming no module (none // is written without one, but a person's hand might) matches any module on its node. func sameProvider(bound, chose mcat.Chosen) bool { return bound.Node == chose.Node && (bound.Module == "" || bound.Module == chose.Module) } // boundNeed is the need answered by the recorded provider, or why it cannot be. func boundNeed(n mcat.Needed, bound mcat.Chosen, catalogue map[string]mcat.Manifest, node mcat.Node, world mcat.World, here func(string) bool) (mcat.Needed, bool, string) { held := mcat.Needed{Name: n.Name, For: n.For, Local: n.Local, KeepsData: true} if bound.Node == node.Name { m, known := catalogue[bound.Module] if !known || !here(bound.Module) || !providesAt(m, n.Name, mcat.ScopeMesh) { return mcat.Needed{}, false, fmt.Sprintf("%s no longer runs on %s", bound.Module, node.Name) } at := node.At if at == "" { at = "127.0.0.1" } held.From, held.At, held.Module = node.Name, at, m.Module held.Serves, held.SharedOwn, held.Identity = servedByOne(m, n.Name), sharedByOne(m, n.Name), m.IdentityBoundOf(n.Name) held.CredentialForm, held.CredentialTool = m.CredentialFormOf(n.Name), m.CredentialToolOf(n.Name) return held, true, "" } matching := chosenAmong(bound, world.Offered[n.Name]) if len(matching) != 1 { return mcat.Needed{}, false, fmt.Sprintf("%s no longer provides it", bound) } p := matching[0] if node.At == "" || p.At == "" { return mcat.Needed{}, false, fmt.Sprintf("%s and %s are not both on the private network", node.Name, p.Node) } identity := mcat.DefaultIdentityBound if pm, known := catalogue[p.Module]; known { held.SharedOwn, _ = pm.SharedCredentialOf(n.Name) held.CredentialForm, held.CredentialTool = pm.CredentialFormOf(n.Name), pm.CredentialToolOf(n.Name) identity = pm.IdentityBoundOf(n.Name) } held.From, held.At, held.Module, held.Serves, held.Identity = p.Node, p.At, p.Module, p.Serves, identity held.LeftOut = p.LeftOut return held, true, "" } // BindsFrom is the providers this resolution binds a pair credential's need to — the provision, the // module that requires it and the credential's local name — answered by a machine rather than by a // record. None means this machine states no such binding. func BindsFrom(r mcat.Resolution, provision, module, local string) []string { var from []string for _, n := range r.Needs { if n.ByRecord || n.Name != provision || n.For != module || n.Local != local { continue } if !slices.Contains(from, n.From) { from = append(from, n.From) } } return from }