End an unassigned key with ended and remove no key of the chain (issue 1093)
ADR 0408 section 4 says no key of the chain is removed, a delete marker included. The Backend loses Remove and the Chain RemoveAssignment. An unassignment, and the end of a value only pulled in by another, write the value again with ended set, by compare-and-set; assigning it again writes a fresh standing value over it. A value with ended or a state is no assignment (Assignment.Stands) to the assignment lists, the joins, forgetting a module, seat holdings, the given values, the move and the assignment-changed event.
This commit is contained in:
@@ -16,9 +16,9 @@ import (
|
||||
|
||||
// Assignments on the bus (novox/hq ADR 0408, issue 1093): once the kind `assignments` is moved (chain.go) an assignment is
|
||||
// a key `<node>.<module>` of the assignments bucket, written by compare-and-set by the controller that holds the lease,
|
||||
// and every question the joins of the store answered is answered from the bucket. **Live values only** in this delivery: an
|
||||
// unassignment ends the key with a marker, as it removed the row, and no `ended` or `pending` mark is written; the
|
||||
// delivery after it keeps an unassigned key (issue 1096).
|
||||
// and every question the joins of the store answered is answered from the bucket. **No key is removed** (ADR 0408 §4): an
|
||||
// unassignment writes the value again with `ended` set, and a value with `ended` or a `state` is no assignment to any reader
|
||||
// here (chain.Assignment.Stands). No `pending` state is written yet (issue 1094).
|
||||
|
||||
// KindAssignments is the kind of the chain that is each module assigned to a node, with the build it runs there.
|
||||
const KindAssignments = "assignments"
|
||||
@@ -32,8 +32,9 @@ type assignRow struct {
|
||||
}
|
||||
|
||||
// assignments is every assignment, by node then module: from the bucket when the kind is moved, else from the store.
|
||||
// A key whose value names no time assigned is a module only pulled in by another (issue 765): no assignment. One on a
|
||||
// machine the store does not know is no assignment either, as the store took a removed machine's.
|
||||
// A key whose value names no time assigned is a module only pulled in by another (issue 765), and one that has ended or
|
||||
// waits in a state is gone: no assignment. One on a machine the store does not know is none either, as the store took a
|
||||
// removed machine's.
|
||||
func (i *Inventory) assignments(ctx context.Context) ([]assignRow, error) {
|
||||
c, on, err := i.ChainReads(ctx, KindAssignments)
|
||||
if err != nil {
|
||||
@@ -66,7 +67,7 @@ func (i *Inventory) assignments(ctx context.Context) ([]assignRow, error) {
|
||||
}
|
||||
var out []assignRow
|
||||
for _, k := range all {
|
||||
if k.Value.Assigned.IsZero() || !nodes[k.Value.Node] {
|
||||
if k.Value.Assigned.IsZero() || !k.Value.Stands() || !nodes[k.Value.Node] {
|
||||
continue
|
||||
}
|
||||
out = append(out, assignRow{k.Value.Node, k.Value.Module, k.Value.Assigned})
|
||||
@@ -163,16 +164,21 @@ func (i *Inventory) chainAssign(ctx context.Context, c *chain.Chain, node Node,
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if found && !cur.Value.Assigned.IsZero() {
|
||||
if found && !cur.Value.Assigned.IsZero() && cur.Value.Stands() {
|
||||
return false, nil // one assignment of a module per machine: a repeat is absorbed (novox/hq ADR 0115)
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if found {
|
||||
// A module only pulled in by another becomes an assignment.
|
||||
fresh := chain.Assignment{Node: node.Name, Module: module, Assigned: now}
|
||||
switch {
|
||||
case found && cur.Value.Stands():
|
||||
// A module only pulled in by another becomes an assignment, keeping what it holds.
|
||||
cur.Value.Assigned = now
|
||||
_, err = c.Assignments.Update(ctx, key, cur.Value, cur.Revision)
|
||||
} else {
|
||||
_, err = c.Assignments.Create(ctx, key, chain.Assignment{Node: node.Name, Module: module, Assigned: now})
|
||||
case found:
|
||||
// One that ended is assigned again: a fresh value over it, the ended one staying in history.
|
||||
_, err = c.Assignments.Update(ctx, key, fresh, cur.Revision)
|
||||
default:
|
||||
_, err = c.Assignments.Create(ctx, key, fresh)
|
||||
}
|
||||
if errors.Is(err, chain.ErrMoved) || errors.Is(err, chain.ErrExists) {
|
||||
continue
|
||||
@@ -188,7 +194,7 @@ func (i *Inventory) chainAssign(ctx context.Context, c *chain.Chain, node Node,
|
||||
return false, fmt.Errorf("the assignment of %s to %s kept moving under the write", module, node.Name)
|
||||
}
|
||||
|
||||
// chainUnassign takes a module off a machine: the key ends with a marker, its earlier values stay.
|
||||
// chainUnassign takes a module off a machine: the value is written again with `ended`, the key stays.
|
||||
func (i *Inventory) chainUnassign(ctx context.Context, c *chain.Chain, node Node, module string) error {
|
||||
key := assignmentKey(node.Name, module)
|
||||
for try := 0; try < casTries; try++ {
|
||||
@@ -196,18 +202,16 @@ func (i *Inventory) chainUnassign(ctx context.Context, c *chain.Chain, node Node
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found || cur.Value.Assigned.IsZero() {
|
||||
if !found || cur.Value.Assigned.IsZero() || !cur.Value.Stands() {
|
||||
return fmt.Errorf("%s is not assigned to %s", module, node.Name)
|
||||
}
|
||||
if err := c.RemoveAssignment(ctx, key, cur.Revision); errors.Is(err, chain.ErrMoved) {
|
||||
cur.Value.Ended = time.Now().UTC()
|
||||
if _, err := c.Assignments.Update(ctx, key, cur.Value, cur.Revision); errors.Is(err, chain.ErrMoved) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("%s is not taken off %s on the bus: %w", module, node.Name, err)
|
||||
}
|
||||
if err := i.raiseGeneration(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return i.raiseGeneration(ctx)
|
||||
}
|
||||
return fmt.Errorf("the assignment of %s to %s kept moving under the write", module, node.Name)
|
||||
}
|
||||
@@ -281,13 +285,19 @@ func (i *Inventory) KeepAssignmentPlans(ctx context.Context) (written, removed i
|
||||
return written, removed, err
|
||||
}
|
||||
cur, has := have[key]
|
||||
if has && sameJSON(cur.Value.Provisioning, next.Provisioning) && cur.Value.RequiredBy == next.RequiredBy {
|
||||
standing := has && cur.Value.Stands()
|
||||
if standing && sameJSON(cur.Value.Provisioning, next.Provisioning) && cur.Value.RequiredBy == next.RequiredBy {
|
||||
continue
|
||||
}
|
||||
if !has {
|
||||
switch {
|
||||
case !has:
|
||||
next.Assigned = time.Time{}
|
||||
_, err = c.Assignments.Create(ctx, key, next)
|
||||
} else {
|
||||
case !standing:
|
||||
// A value that ended, pulled in again: a fresh one over it, the ended one staying in history.
|
||||
next.Assigned = time.Time{}
|
||||
_, err = c.Assignments.Update(ctx, key, next, cur.Revision)
|
||||
default:
|
||||
cur.Value.RequiredBy, cur.Value.Provisioning = next.RequiredBy, next.Provisioning
|
||||
_, err = c.Assignments.Update(ctx, key, cur.Value, cur.Revision)
|
||||
}
|
||||
@@ -300,11 +310,12 @@ func (i *Inventory) KeepAssignmentPlans(ctx context.Context) (written, removed i
|
||||
written++
|
||||
}
|
||||
for key, cur := range have {
|
||||
if _, wanted := want[key]; wanted || !cur.Value.Assigned.IsZero() {
|
||||
if _, wanted := want[key]; wanted || !cur.Value.Assigned.IsZero() || !cur.Value.Stands() {
|
||||
continue
|
||||
}
|
||||
// A value of a module no longer pulled in, which no assignment names.
|
||||
if err := c.RemoveAssignment(ctx, key, cur.Revision); errors.Is(err, chain.ErrMoved) {
|
||||
// A value of a module no longer pulled in, which no assignment names: it ends, and stays.
|
||||
cur.Value.Ended = time.Now().UTC()
|
||||
if _, err := c.Assignments.Update(ctx, key, cur.Value, cur.Revision); errors.Is(err, chain.ErrMoved) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return written, removed, fmt.Errorf("the plan of %s is not ended: %w", key, err)
|
||||
@@ -331,7 +342,8 @@ type KeptValue struct {
|
||||
Revision uint64
|
||||
}
|
||||
|
||||
// KeptAssignments is every key of the assignments bucket with its value, and false when the assignments are not on the bus.
|
||||
// KeptAssignments is every key of the assignments bucket that holds a value that stands, and false when the assignments are
|
||||
// not on the bus.
|
||||
// What the serving controller compares from one look to the next to say a key changed (`assignment-changed`).
|
||||
func (i *Inventory) KeptAssignments(ctx context.Context) (map[string]KeptValue, bool, error) {
|
||||
c, on, err := i.ChainReads(ctx, KindAssignments)
|
||||
@@ -344,6 +356,9 @@ func (i *Inventory) KeptAssignments(ctx context.Context) (map[string]KeptValue,
|
||||
}
|
||||
out := make(map[string]KeptValue, len(all))
|
||||
for key, k := range all {
|
||||
if !k.Value.Stands() {
|
||||
continue // an ended value is no assignment: for what is said of it, the key has gone
|
||||
}
|
||||
body, err := json.Marshal(k.Value)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
|
||||
Reference in New Issue
Block a user