package build import ( "fmt" "sort" "strings" "git.novox.be/novox/mesh-sdk/go/delivery" ) // ChooseNodes picks up to want build nodes for a language (ADR 0478 clause 3): the nodes whose toolchains key reports the // version the nodes agree on, the ones with room under their build capacity first (places), then the fewest lanes the // builder has running or chosen there, a tie broken by the node's name. A place is reserved on each node chosen, in the // same step as the choice, so builds choosing at once do not choose the same full node while another has room; the // caller takes the place (Places.Acquire) or gives it up (Places.Cancel). A node at its capacity is still chosen when // the others are too (fewer than want have room): its lane waits for a place. A node qualifies only when qualifies is true // of it (online and its build module healthy; nil means every node does). It answers the toolchain all the reporting nodes // agree on, qualifying or not, as the indexer computes it. When nothing is reported, or the nodes report different // versions (so the indexer agreed none), it fails naming the language and why; when no reporting node qualifies it fails // naming the nodes. A node whose build module refuses on its toolchain refuses the step, and the build fails naming it. func ChooseNodes(language string, entries []delivery.NodeToolchain, places *Places, qualifies func(node string) bool, want int) ([]string, delivery.Toolchain2, error) { tc, none := delivery.AgreedToolchain(entries) if none != "" { return nil, delivery.Toolchain2{}, fmt.Errorf("no %s build node qualifies: %s", language, none) } nodes, err := choose(language, entries, places, qualifies, want) return nodes, tc, err } // ChooseAnyNodes is ChooseNodes for artifacts that need no toolchain, such as an archive (ADR 0478 clause 1): the // build module's nodes agree on nothing, so any node of the build module that qualifies is chosen, by capacity and load only. func ChooseAnyNodes(language string, entries []delivery.NodeToolchain, places *Places, qualifies func(node string) bool, want int) ([]string, error) { return choose(language, entries, places, qualifies, want) } func choose(language string, entries []delivery.NodeToolchain, places *Places, qualifies func(node string) bool, want int) ([]string, error) { nodes := make([]string, 0, len(entries)) var reporting []string seen := map[string]bool{} for _, e := range entries { if seen[e.Node] { continue } seen[e.Node] = true reporting = append(reporting, e.Node) if qualifies == nil || qualifies(e.Node) { nodes = append(nodes, e.Node) } } if len(nodes) == 0 { sort.Strings(reporting) return nil, fmt.Errorf("no %s build node qualifies: %s report the toolchain, and none is online with a healthy build module", language, strings.Join(reporting, ", ")) } if places == nil { places = &Places{} } return places.choose(nodes, want), nil } // DigestsDifferPrefix opens the error of a build whose nodes built an artifact to different digests (ADR 0478 clause 4): // such a key is not built again as a new revision, since the same source on the same toolchains gave two answers and // building again settles nothing; it waits for another fingerprint. const DigestsDifferPrefix = "digests differ:" // Agree is the one digest every node built an artifact to (ADR 0478 clause 4), or an error naming each node and its // digest when they differ. One node's digest agrees with itself. func Agree(artifact string, byNode map[string]string) (string, error) { nodes := make([]string, 0, len(byNode)) for n := range byNode { nodes = append(nodes, n) } sort.Strings(nodes) var first string for i, n := range nodes { if i == 0 { first = byNode[n] continue } if byNode[n] != first { msg := fmt.Sprintf("%s the nodes built %s to different digests:", DigestsDifferPrefix, artifact) for _, n := range nodes { msg += fmt.Sprintf(" %s %s;", n, byNode[n]) } return "", fmt.Errorf("%s", msg[:len(msg)-1]) } } return first, nil }