1090/1092: the move tests pick each report by kind and expect three, all already, on the second run. 1090-1093: under the share lock the switch reads the rows again and writes every key whose value differs from its row (and ends an assignment the store dropped) before recording the kind, so an update the old controller made after the first pass is not lost. 1093: route-proxy is pinned at mesh-sdk f185aedb and vendored; the controller uses only the SDK's asks package. KeepAssignmentPlans decides each key by keepStep, which never makes or revives an assignment. AssignmentsMirrorToWrite refuses once the assignments are moved.
214 lines
9.1 KiB
Go
214 lines
9.1 KiB
Go
package inventory
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// The values of the controller's record mirrors, built from the store (novox/hq ADR 0289, design 54 §1,
|
|
// design 57 package F3).
|
|
//
|
|
// **Each value is built from a named list of fields, and nothing else** (design 54 §1): no epoch, no time
|
|
// of writing and no time of the last look, so any lease holder builds the same bytes from the same record
|
|
// and a pass over unchanged records writes nothing (ADR 0289 §1, §4). The encoding is fixed: the fields in
|
|
// their struct's order, a declaration's maps sorted.
|
|
|
|
// AssignmentMirrored is one assignment as the assignments mirror holds it: the module, the node, and since
|
|
// when it is assigned there. Design 54 §1 also lists whether it is pending, the builds sent and running and
|
|
// the seats and provisions it holds there; those join with issue 419, which builds the rest of design 54.
|
|
type AssignmentMirrored struct {
|
|
Node string `json:"node"`
|
|
Module string `json:"module"`
|
|
Assigned time.Time `json:"assigned,omitzero"`
|
|
// RequiredBy is, for a module only pulled in by another and handed to the provisioner on this node (issue 765),
|
|
// the module that requires it there; such a value has no time assigned. Absent for an assignment.
|
|
RequiredBy string `json:"required-by,omitempty"`
|
|
// Provisioning is the plan of a module handed to the provisioner on this node (novox/hq ADR 0349 clause 4,
|
|
// design 57 P5): absent for every other, so their values are the bytes they were.
|
|
Provisioning *Provisioning `json:"provisioning,omitempty"`
|
|
}
|
|
|
|
// DeclaredConfigMirrored is one registered module's config declarations as the declared-config mirror holds
|
|
// them: the module, and each key its manifest declares — under `config`, or under its old name `settings` for a
|
|
// module not yet moved (ADR 0348 §1) — with that declaration's listed fields. An empty object when the module
|
|
// declares none: registered, declaring nothing.
|
|
type DeclaredConfigMirrored struct {
|
|
Module string `json:"module"`
|
|
Settings map[string]DeclaredMirrored `json:"settings"`
|
|
}
|
|
|
|
// DeclaredMirrored is one key's declaration as the mirror holds it: **its listed fields and no other**
|
|
// (ADR 0289 §12), and every field a declaration has (catalogue.SettingDeclaration), under the same names. The
|
|
// mirror holds what the module's manifest declares about its config values, and mesh-config validates and
|
|
// resolves from it (ADR 0356 §4, ADR 0348 §4, §9), so it carries the whole declaration: kind, default and why
|
|
// of the settings form, and type, description, validation, trusted, predefined, required and runtime of the
|
|
// config form. None is a secret: a secret lives in the vault, never in config, and the catalogue refuses a
|
|
// default for a key whose name says secret. A field the manifest gains reaches the bus only once it is added
|
|
// here, deliberately: a test fails until it is listed. The config form's fields are left out when unset, so a
|
|
// module not yet moved is mirrored as it was before they were carried.
|
|
type DeclaredMirrored struct {
|
|
Kind string `json:"kind"`
|
|
Default json.RawMessage `json:"default"`
|
|
Why string `json:"why"`
|
|
Type string `json:"type,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Enum json.RawMessage `json:"enum,omitempty"`
|
|
Minimum *float64 `json:"minimum,omitempty"`
|
|
Maximum *float64 `json:"maximum,omitempty"`
|
|
Pattern string `json:"pattern,omitempty"`
|
|
Trusted bool `json:"trusted,omitempty"`
|
|
Predefined string `json:"predefined,omitempty"`
|
|
Required bool `json:"required,omitempty"`
|
|
Runtime bool `json:"runtime,omitempty"`
|
|
}
|
|
|
|
// DeclaredMirroredFields are the fields of a declaration the mirror carries, by their manifest names.
|
|
var DeclaredMirroredFields = []string{"kind", "default", "why", "type", "description", "enum", "minimum", "maximum",
|
|
"pattern", "trusted", "predefined", "required", "runtime"}
|
|
|
|
// ErrAssignmentsMoved is a write of the store's mirror of the assignments, refused because the assignments are keys of the bus.
|
|
var ErrAssignmentsMoved = errors.New("the assignments are keys of the bus, and the store's rows are no longer what they say")
|
|
|
|
// AssignmentsMirrorToWrite is AssignmentsMirror for the mirror's writer, which lays what it returns over the bucket and
|
|
// ends the keys it does not return: **refused once the assignments are moved** (ADR 0408 §4), asked at the moment of the
|
|
// write, so a tick that decided before the switch cannot lay mirror-shaped values over the links after it.
|
|
func (i *Inventory) AssignmentsMirrorToWrite(ctx context.Context) (map[string][]byte, error) {
|
|
if _, on, err := i.ChainReads(ctx, KindAssignments); err != nil {
|
|
return nil, err
|
|
} else if on {
|
|
return nil, ErrAssignmentsMoved
|
|
}
|
|
return i.AssignmentsMirror(ctx)
|
|
}
|
|
|
|
// AssignmentsMirror is every assignment's mirrored value, keyed `<node>.<module>`, with the provisioner's plan of
|
|
// each assignment of a module handed to it, and a value of its own for each module handed to it on a node that
|
|
// nothing assigned there, only pulled in by another (issue 765).
|
|
func (i *Inventory) AssignmentsMirror(ctx context.Context) (map[string][]byte, error) {
|
|
var plans map[string]Provisioning
|
|
if i.provisioningPlans != nil {
|
|
var err error
|
|
if plans, err = i.provisioningPlans(ctx); err != nil {
|
|
return nil, fmt.Errorf("composing the provisioner's plans: %w", err)
|
|
}
|
|
}
|
|
assignments, err := i.assignmentsMirrored(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return AssignmentValues(assignments, plans)
|
|
}
|
|
|
|
func (i *Inventory) assignmentsMirrored(ctx context.Context) ([]AssignmentMirrored, error) {
|
|
rows, err := i.assignments(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]AssignmentMirrored, 0, len(rows))
|
|
for _, a := range rows {
|
|
out = append(out, AssignmentMirrored{Node: a.Node, Module: a.Module, Assigned: a.Assigned.UTC()})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// DeclaredConfigMirror is every registered module's mirrored config declarations, keyed by the module.
|
|
func (i *Inventory) DeclaredConfigMirror(ctx context.Context) (map[string][]byte, error) {
|
|
rows, err := i.store.Pool().Query(ctx,
|
|
`select name, coalesce(manifest->'config', manifest->'settings', '{}'::jsonb) from module`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := map[string][]byte{}
|
|
for rows.Next() {
|
|
var (
|
|
d DeclaredConfigMirrored
|
|
raw []byte
|
|
)
|
|
if err := rows.Scan(&d.Module, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
if d.Settings, err = declaredMirrored(raw); err != nil {
|
|
return nil, fmt.Errorf("the declarations of %s cannot be read: %w", d.Module, err)
|
|
}
|
|
value, err := json.Marshal(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[d.Module] = value
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// declaredMirrored is a manifest's config declarations, keyed by the value, as the mirror holds them: each
|
|
// with its listed fields, a default and an enum encoded the one way.
|
|
func declaredMirrored(raw []byte) (map[string]DeclaredMirrored, error) {
|
|
var declared map[string]DeclaredMirrored
|
|
if err := json.Unmarshal(raw, &declared); err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]DeclaredMirrored{}
|
|
for key, decl := range declared {
|
|
var err error
|
|
if len(decl.Default) > 0 {
|
|
if decl.Default, err = canonical(decl.Default); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(decl.Enum) > 0 {
|
|
if decl.Enum, err = canonical(decl.Enum); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
out[key] = decl
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// canonical is a JSON document encoded the one way: objects' keys sorted, numbers as written.
|
|
func canonical(raw []byte) ([]byte, error) {
|
|
dec := json.NewDecoder(bytes.NewReader(raw))
|
|
dec.UseNumber()
|
|
var v any
|
|
if err := dec.Decode(&v); err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(v)
|
|
}
|
|
|
|
// PredefinedMirrorKey is the predefined-keys mirror's one key: the list kept is one record.
|
|
const PredefinedMirrorKey = "list"
|
|
|
|
// PredefinedMirrored is the list of predefined keys kept as its mirror holds it (design 57 K3, issue 662): the
|
|
// keys, canonical, and the trunk build of the catalogue they came from, by its commit and generation, so a reader
|
|
// says which list it holds. Not when it was kept: any lease holder builds the same bytes from the same record.
|
|
type PredefinedMirrored struct {
|
|
Keys json.RawMessage `json:"keys"`
|
|
Repository string `json:"repository"`
|
|
Commit string `json:"commit"`
|
|
Generation int `json:"generation"`
|
|
Build string `json:"build"`
|
|
}
|
|
|
|
// PredefinedMirror is the list of predefined keys kept, under PredefinedMirrorKey; no key while none is kept.
|
|
func (i *Inventory) PredefinedMirror(ctx context.Context) (map[string][]byte, error) {
|
|
kept, has, err := i.PredefinedKept(ctx)
|
|
if err != nil || !has {
|
|
return map[string][]byte{}, err
|
|
}
|
|
keys, err := kept.Keys.Canonical()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value, err := json.Marshal(PredefinedMirrored{Keys: keys, Repository: kept.Repository, Commit: kept.Commit,
|
|
Generation: kept.Generation, Build: kept.Build})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string][]byte{PredefinedMirrorKey: value}, nil
|
|
}
|