|
|
|
@@ -254,8 +254,10 @@ var ErrAlreadyPending = errors.New("that assignment is already waiting for its b
|
|
|
|
|
// ErrOnTheMachine is a pending assignment of a module that is on the machine already.
|
|
|
|
|
var ErrOnTheMachine = errors.New("that module is on the machine already")
|
|
|
|
|
|
|
|
|
|
func pendingOf(v chain.Assignment) PendingAssignment {
|
|
|
|
|
p := v.Pending
|
|
|
|
|
func pendingOf(v chain.Assignment) PendingAssignment { return pendingRecord(v, *v.Pending) }
|
|
|
|
|
|
|
|
|
|
// pendingRecord is one wait of a key, the one it holds or one it replaced.
|
|
|
|
|
func pendingRecord(v chain.Assignment, p chain.Pending) PendingAssignment {
|
|
|
|
|
return PendingAssignment{ID: p.ID, Node: v.Node, Module: v.Module, Build: p.Build, Repository: p.Repository, Path: p.Path,
|
|
|
|
|
Since: p.Since, State: p.State, Note: p.Note, Settled: p.Settled, Raised: p.Raised, Acknowledged: p.Acknowledged,
|
|
|
|
|
Cleared: p.Cleared}
|
|
|
|
@@ -296,16 +298,39 @@ func (i *Inventory) pendingValues(ctx context.Context) ([]chain.Kept[chain.Assig
|
|
|
|
|
func pendingList(kept []chain.Kept[chain.Assignment], keep func(chain.Pending) bool) []PendingAssignment {
|
|
|
|
|
var out []PendingAssignment
|
|
|
|
|
for _, k := range kept {
|
|
|
|
|
if keep(*k.Value.Pending) {
|
|
|
|
|
out = append(out, pendingOf(k.Value))
|
|
|
|
|
for _, p := range pendingWaits(k.Value) {
|
|
|
|
|
if keep(p) {
|
|
|
|
|
out = append(out, pendingRecord(k.Value, p))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sort.SliceStable(out, func(a, b int) bool {
|
|
|
|
|
if !out[a].Since.Equal(out[b].Since) {
|
|
|
|
|
return out[a].Since.Before(out[b].Since)
|
|
|
|
|
}
|
|
|
|
|
return out[a].ID < out[b].ID
|
|
|
|
|
})
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// changePending changes the pending assignment of an id by compare-and-set on the key that holds it, read again when it
|
|
|
|
|
// moved: false when no key holds that id now, or change says no.
|
|
|
|
|
func (i *Inventory) changePending(ctx context.Context, id string, change func(v *chain.Assignment) bool) (bool, error) {
|
|
|
|
|
// pendingWaits is the wait a value holds and the earlier ones it carries, each without the others.
|
|
|
|
|
func pendingWaits(v chain.Assignment) []chain.Pending {
|
|
|
|
|
if v.Pending == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
out := make([]chain.Pending, 0, 1+len(v.Pending.Earlier))
|
|
|
|
|
for _, e := range v.Pending.Earlier {
|
|
|
|
|
e.Earlier = nil
|
|
|
|
|
out = append(out, e)
|
|
|
|
|
}
|
|
|
|
|
held := *v.Pending
|
|
|
|
|
held.Earlier = nil
|
|
|
|
|
return append(out, held)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// changePending changes the pending assignment of an id — the wait a key holds or one it carries from before — by
|
|
|
|
|
// compare-and-set on the key that holds it, read again when it moved: false when no key holds that id now, or change says no.
|
|
|
|
|
func (i *Inventory) changePending(ctx context.Context, id string, change func(p *chain.Pending) bool) (bool, error) {
|
|
|
|
|
c, err := i.theChain(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, err
|
|
|
|
@@ -316,21 +341,34 @@ func (i *Inventory) changePending(ctx context.Context, id string, change func(v
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
var at *chain.Kept[chain.Assignment]
|
|
|
|
|
var target *chain.Pending
|
|
|
|
|
for k := range held {
|
|
|
|
|
if held[k].Value.Pending.ID == id {
|
|
|
|
|
at = &held[k]
|
|
|
|
|
v := held[k].Value
|
|
|
|
|
p := *v.Pending
|
|
|
|
|
p.Earlier = append([]chain.Pending(nil), p.Earlier...)
|
|
|
|
|
v.Pending = &p
|
|
|
|
|
switch {
|
|
|
|
|
case p.ID == id:
|
|
|
|
|
at, target = &held[k], v.Pending
|
|
|
|
|
default:
|
|
|
|
|
for e := range v.Pending.Earlier {
|
|
|
|
|
if v.Pending.Earlier[e].ID == id {
|
|
|
|
|
at, target = &held[k], &v.Pending.Earlier[e]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if at != nil {
|
|
|
|
|
held[k].Value = v
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if at == nil {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
v := at.Value
|
|
|
|
|
p := *v.Pending
|
|
|
|
|
v.Pending = &p
|
|
|
|
|
if !change(&v) {
|
|
|
|
|
if !change(target) {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
if _, err := c.Assignments.Update(ctx, at.Key, v, at.Revision); errors.Is(err, chain.ErrMoved) {
|
|
|
|
|
if _, err := c.Assignments.Update(ctx, at.Key, at.Value, at.Revision); errors.Is(err, chain.ErrMoved) {
|
|
|
|
|
continue
|
|
|
|
|
} else if err != nil {
|
|
|
|
|
return false, err
|
|
|
|
@@ -368,7 +406,8 @@ func (i *Inventory) RecordPending(ctx context.Context, p PendingAssignment) (Pen
|
|
|
|
|
p.ID, p.Since, p.State = fmt.Sprintf("p-%d", since.UnixNano()), since, PendingWaiting
|
|
|
|
|
p.Path = strings.Trim(p.Path, "/")
|
|
|
|
|
value := chain.Assignment{Node: node.Name, Module: p.Module, State: chain.StatePending, Pending: &chain.Pending{
|
|
|
|
|
ID: p.ID, State: PendingWaiting, Build: p.Build, Repository: p.Repository, Path: p.Path, Since: since}}
|
|
|
|
|
ID: p.ID, State: PendingWaiting, Build: p.Build, Repository: p.Repository, Path: p.Path, Since: since,
|
|
|
|
|
Earlier: unansweredWaits(cur.Value, found)}}
|
|
|
|
|
if found {
|
|
|
|
|
_, err = c.Assignments.Update(ctx, key, value, cur.Revision)
|
|
|
|
|
} else {
|
|
|
|
@@ -387,11 +426,11 @@ func (i *Inventory) RecordPending(ctx context.Context, p PendingAssignment) (Pen
|
|
|
|
|
|
|
|
|
|
// RepointPending makes a waiting pending assignment wait for another build. False when it no longer waits.
|
|
|
|
|
func (i *Inventory) RepointPending(ctx context.Context, id string, build, repository, sourcePath string) (bool, error) {
|
|
|
|
|
return i.changePending(ctx, id, func(v *chain.Assignment) bool {
|
|
|
|
|
if v.Pending.State != PendingWaiting {
|
|
|
|
|
return i.changePending(ctx, id, func(p *chain.Pending) bool {
|
|
|
|
|
if p.State != PendingWaiting {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
v.Pending.Build, v.Pending.Repository, v.Pending.Path = build, repository, strings.Trim(sourcePath, "/")
|
|
|
|
|
p.Build, p.Repository, p.Path = build, repository, strings.Trim(sourcePath, "/")
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
@@ -411,8 +450,8 @@ func (i *Inventory) Pending(ctx context.Context, endedSince time.Time) ([]Pendin
|
|
|
|
|
}), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PendingFor is the pending assignment of one module to one machine: the one its key holds now, which is the newest
|
|
|
|
|
// (an earlier one is read from the key's history, which nothing here needs).
|
|
|
|
|
// PendingFor is every pending assignment of one module to one machine the key still has to say, newest first: the wait it
|
|
|
|
|
// holds now and the unanswered ones it replaced.
|
|
|
|
|
func (i *Inventory) PendingFor(ctx context.Context, node, module string) ([]PendingAssignment, error) {
|
|
|
|
|
held, err := i.pendingValues(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -421,9 +460,13 @@ func (i *Inventory) PendingFor(ctx context.Context, node, module string) ([]Pend
|
|
|
|
|
var out []PendingAssignment
|
|
|
|
|
for _, k := range held {
|
|
|
|
|
if k.Value.Node == node && k.Value.Module == module {
|
|
|
|
|
out = append(out, pendingOf(k.Value))
|
|
|
|
|
for _, w := range pendingWaits(k.Value) {
|
|
|
|
|
out = append(out, pendingRecord(k.Value, w))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Newest first.
|
|
|
|
|
sort.SliceStable(out, func(a, b int) bool { return out[a].Since.After(out[b].Since) })
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -465,8 +508,8 @@ func (i *Inventory) MarkPending(ctx context.Context, id string, what string) err
|
|
|
|
|
return errors.New("a pending assignment is marked raised, acknowledged or cleared")
|
|
|
|
|
}
|
|
|
|
|
now := time.Now().UTC()
|
|
|
|
|
_, err := i.changePending(ctx, id, func(v *chain.Assignment) bool {
|
|
|
|
|
mark := map[string]**time.Time{"raised": &v.Pending.Raised, "acknowledged": &v.Pending.Acknowledged, "cleared": &v.Pending.Cleared}[what]
|
|
|
|
|
_, err := i.changePending(ctx, id, func(p *chain.Pending) bool {
|
|
|
|
|
mark := map[string]**time.Time{"raised": &p.Raised, "acknowledged": &p.Acknowledged, "cleared": &p.Cleared}[what]
|
|
|
|
|
if *mark != nil {
|
|
|
|
|
return false // the first stamp stands
|
|
|
|
|
}
|
|
|
|
@@ -480,22 +523,22 @@ func (i *Inventory) MarkPending(ctx context.Context, id string, what string) err
|
|
|
|
|
// it is made. False when it no longer waits: another process took it, or it was withdrawn.
|
|
|
|
|
func (i *Inventory) ClaimPending(ctx context.Context, id string) (bool, error) {
|
|
|
|
|
now := time.Now().UTC()
|
|
|
|
|
return i.changePending(ctx, id, func(v *chain.Assignment) bool {
|
|
|
|
|
if !chain.CanMove(v.Pending.State, PendingApplying) {
|
|
|
|
|
return i.changePending(ctx, id, func(p *chain.Pending) bool {
|
|
|
|
|
if !chain.CanMove(p.State, PendingApplying) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
v.Pending.State, v.Pending.Settled = PendingApplying, &now
|
|
|
|
|
p.State, p.Settled = PendingApplying, &now
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReleaseClaim puts a claimed pending assignment back to waiting.
|
|
|
|
|
func (i *Inventory) ReleaseClaim(ctx context.Context, id string) error {
|
|
|
|
|
_, err := i.changePending(ctx, id, func(v *chain.Assignment) bool {
|
|
|
|
|
if v.Pending.State != PendingApplying {
|
|
|
|
|
_, err := i.changePending(ctx, id, func(p *chain.Pending) bool {
|
|
|
|
|
if p.State != PendingApplying {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
v.Pending.State, v.Pending.Settled = PendingWaiting, nil
|
|
|
|
|
p.State, p.Settled = PendingWaiting, nil
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
return err
|
|
|
|
@@ -508,17 +551,17 @@ func (i *Inventory) SettlePending(ctx context.Context, id string, from, state, n
|
|
|
|
|
return false, errors.New("a pending assignment is settled to applied, refused, expired or withdrawn")
|
|
|
|
|
}
|
|
|
|
|
now := time.Now().UTC()
|
|
|
|
|
return i.changePending(ctx, id, func(v *chain.Assignment) bool {
|
|
|
|
|
if v.Pending.State != from || !chain.CanSettle(from, state) {
|
|
|
|
|
return i.changePending(ctx, id, func(p *chain.Pending) bool {
|
|
|
|
|
if p.State != from || !chain.CanSettle(from, state) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
v.Pending.State, v.Pending.Note, v.Pending.Settled = state, note, &now
|
|
|
|
|
p.State, p.Note, p.Settled = state, note, &now
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PendingKnown says which of these pending assignments are still on record: a key holds them now. One a newer pending
|
|
|
|
|
// assignment of its key replaced is not.
|
|
|
|
|
// PendingKnown says which of these pending assignments are still on record: a key holds them now, or carries them as
|
|
|
|
|
// unanswered earlier waits.
|
|
|
|
|
func (i *Inventory) PendingKnown(ctx context.Context, ids []string) (map[string]bool, error) {
|
|
|
|
|
held, err := i.pendingValues(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -530,9 +573,26 @@ func (i *Inventory) PendingKnown(ctx context.Context, ids []string) (map[string]
|
|
|
|
|
}
|
|
|
|
|
out := map[string]bool{}
|
|
|
|
|
for _, k := range held {
|
|
|
|
|
if want[k.Value.Pending.ID] {
|
|
|
|
|
out[k.Value.Pending.ID] = true
|
|
|
|
|
for _, w := range pendingWaits(k.Value) {
|
|
|
|
|
if want[w.ID] {
|
|
|
|
|
out[w.ID] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unansweredWaits are the waits a key holds that ended unmade and whose condition nobody has answered yet — taken back or
|
|
|
|
|
// cleared — which a new wait replacing them carries, so the operator is not left without it (issue 1108).
|
|
|
|
|
func unansweredWaits(v chain.Assignment, found bool) []chain.Pending {
|
|
|
|
|
if !found || v.State != chain.StatePending {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
var out []chain.Pending
|
|
|
|
|
for _, w := range pendingWaits(v) {
|
|
|
|
|
if (w.State == PendingExpired || w.State == PendingRefused) && w.Cleared == nil && w.Acknowledged == nil {
|
|
|
|
|
out = append(out, w)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|