Say an apply's report even when the apply ends the link
A host that delivered its own successor stood aside before the report of that apply was published, so it failed with 'context canceled' and the release plan waited for a report that never came (novox/hq issue 264). Reports are now published on a context the stand-aside does not cancel, and the last apply's report is kept until the broker takes it and said again on the next link, so a crash between apply and report is covered too.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// The report of the last apply of a declaration from the mesh, kept until the mesh has taken it.
|
||||
//
|
||||
// **An apply the mesh never heard about is one it waits on for ever.** The controller's release
|
||||
// plan waits for a machine to report the exact declaration it was sent; a report lost between the
|
||||
// apply and the publish — a host standing aside for its successor, a crash, a power cut — leaves the
|
||||
// machine applied and the plan waiting until somebody pushes by hand (novox/hq issue 264). The
|
||||
// declaration itself is kept (declared.go), and re-applying it would say something new about the
|
||||
// machine; what was lost is what *that* apply did, so that is what is kept, exactly as it was made,
|
||||
// and said again once the host is linked.
|
||||
//
|
||||
// Opaque bytes here: the report is the link's word, and the store keeps it without reading it.
|
||||
|
||||
// UnsaidName is where it lives, beside the state.
|
||||
const UnsaidName = "unsaid.json"
|
||||
|
||||
// UnsaidPath is where the unsaid report lives, given where the state lives.
|
||||
func UnsaidPath(statePath string) string {
|
||||
return filepath.Join(filepath.Dir(statePath), UnsaidName)
|
||||
}
|
||||
|
||||
// SaveUnsaid keeps a report the mesh has not taken yet, replacing whatever was kept before: only
|
||||
// the last apply's report is worth saying again, because the mesh compares against what it sent last.
|
||||
func SaveUnsaid(path string, report []byte) error {
|
||||
if len(report) == 0 {
|
||||
return errors.New("refusing to keep an empty report")
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, err := os.CreateTemp(filepath.Dir(path), ".unsaid-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
if err := tmp.Chmod(0o600); err != nil {
|
||||
tmp.Close()
|
||||
return err
|
||||
}
|
||||
if _, err := tmp.Write(report); err != nil {
|
||||
tmp.Close()
|
||||
return err
|
||||
}
|
||||
if err := tmp.Sync(); err != nil {
|
||||
tmp.Close()
|
||||
return err
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp.Name(), path)
|
||||
}
|
||||
|
||||
// ReadUnsaid is the report kept unsaid, or nil when there is none — the ordinary case.
|
||||
func ReadUnsaid(path string) ([]byte, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return raw, err
|
||||
}
|
||||
|
||||
// ClearUnsaid forgets the kept report, once the mesh has taken it. Absent is already clear.
|
||||
func ClearUnsaid(path string) error {
|
||||
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user