Files
mesh-controller/internal/inventory/placement_move.go
T

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
}