Files
mesh-controller/internal/inventory/placement_move.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
2026-10-15 21:27:33 +00:00

107 lines
4.4 KiB
Go

package inventory
import (
"context"
"sort"
"github.com/novox/mesh-controller/internal/catalogue"
)
// **The move of placement** (novox/hq ADR 0348 clause 11, issue 736; design 57: the moves of data run after the
// delivery, each a verb that says what it did). Migration 0097 moved `places`, `accesses` and `networks` out of
// every settings layer; `endpoints`, `expose`, `reach` and `ports` are placement too, and a layer set before this
// controller still holds them among its settings. Composition already reads them as placement wherever they are
// (SettingsFor), so a machine is told the same before the move and after; the move only puts them where they
// belong, so that no settings layer holds a placement key and the settings layers can move into mesh-config (K5).
//
// One transaction: each layer that holds a placement key is kept whole in the history first ("moved", with why),
// its placement keys are laid over the placement kept beside it, and its settings row keeps its other keys, when
// it was set and who set it. Asked again, it moves nothing.
// MovedLayer is one layer the move of placement moved, or would move: whose, and which keys.
type MovedLayer struct {
// Node is the layer's machine, empty for the whole mesh's.
Node string `json:"node"`
Module string `json:"module"`
Keys []string `json:"keys"`
}
// movedWhy is why a layer the move of placement moved went into the history, as migration 0097 says it.
const movedWhy = "moved to placement, ADR 0348 clause 11"
// MovePlacement moves every placement key a settings layer still holds beside it, and says which layers held which,
// the whole mesh's first, then by machine and module. Without apply it moves nothing and says what it would.
func (i *Inventory) MovePlacement(ctx context.Context, apply bool) ([]MovedLayer, error) {
keys := catalogue.PlacementKeys()
tx, err := i.store.Pool().Begin(ctx)
if err != nil {
return nil, err
}
defer func() { _ = tx.Rollback(context.WithoutCancel(ctx)) }()
// Locked for the move, so a set between the read and the write cannot be lost under it.
rows, err := tx.Query(ctx,
`select coalesce(n.name, ''), s.module,
(select array_agg(k order by k) from jsonb_object_keys(s.values) k where k = any($1))
from settings s left join node n on n.id = s.node
where s.values ?| $1
order by s.node is not null, n.name, s.module
for update of s`, keys)
if err != nil {
return nil, err
}
moved := []MovedLayer{}
for rows.Next() {
var m MovedLayer
if err := rows.Scan(&m.Node, &m.Module, &m.Keys); err != nil {
rows.Close()
return nil, err
}
sort.Strings(m.Keys)
moved = append(moved, m)
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
if !apply || len(moved) == 0 {
return moved, nil
}
// Each layer kept whole first, as the history reads a layer: its settings and the placement beside it.
if _, err := tx.Exec(ctx,
`insert into settings_history (node, module, values, set_at, replaced_by, set_by, why)
select s.node, s.module, `+layerWhole+`, s.set_at, 'moved', s.set_by, $2
from settings s where s.values ?| $1`, keys, movedWhy); err != nil {
return nil, err
}
// Laid over what is kept beside the layer: a key the layer still holds was said after anything kept there.
if _, err := tx.Exec(ctx,
`update placement p
set values = p.values || (select jsonb_object_agg(e.key, e.value) from jsonb_each(s.values) e
where e.key = any($1))
from settings s
where s.module = p.module and s.node is not distinct from p.node and s.values ?| $1`, keys); err != nil {
return nil, err
}
if _, err := tx.Exec(ctx,
`insert into placement (node, module, values, set_at, set_by)
select s.node, s.module,
(select jsonb_object_agg(e.key, e.value) from jsonb_each(s.values) e where e.key = any($1)),
s.set_at, s.set_by
from settings s
where s.values ?| $1
and not exists (select 1 from placement p
where p.module = s.module and p.node is not distinct from s.node)`, keys); err != nil {
return nil, err
}
// The layer keeps its other keys, when it was set and who set it; one that held only placement stays, empty.
if _, err := tx.Exec(ctx,
`update settings set values = values - $1::text[] where values ?| $1`, keys); err != nil {
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err
}
return moved, nil
}