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 }