Files
mesh-catalog/modules/zfs/cmd/zfs-tools/hand.go
T
jschoubben 58664eee60 zfs: the hand acts answer started at once and the manifest asks stream-publish, bus-state-calls and ledger-calls (A-43, A-44)
Removes streamHear, HandPoll, HandWait and the stream mark; keeps toolEmit.

Builds: design 42 zfs-owns-zfs §24, §25, lead answers A-43, A-44
2026-11-13 01:52:50 +01:00

99 lines
3.6 KiB
Go

package main
import (
"context"
"fmt"
"strings"
)
// The hand acts (design 42 zfs-owns-zfs §23, §24; A-12, A-17, A-18).
//
// A hand act writes its act into the state `schedule` under its own key {by, why, at} and starts the unit of
// the scheduled run through `<node>/node-service-manager.start` with {scope, unit}; the run reads the key,
// acts under the same lock and guards, and deletes it. The tool does not wait (A-43): once the key is written and
// the unit started it answers "started" at once, naming the act and that `zfs_module_events` reads its outcome, and
// never "done" for what it did not see end. The `why` is kept in the key.
// The acts, as the keys of the state `schedule`.
const (
ActSnapshots = "snapshots"
ActPrune = "prune"
ActApply = "apply"
)
// handActs are the acts of the run a person may start.
var handActs = map[string]bool{ActSnapshots: true, ActPrune: true, ActApply: true}
// StartArgs are what `node-service-manager.start` is called with to run the scheduled run now.
func StartArgs() map[string]any { return map[string]any{"scope": "system", "unit": RunService} }
// HandAnswer is what a hand act's tool answers.
type HandAnswer struct {
Act string `json:"act"`
// Outcome is "started": the act was started and its end is read from zfs_module_events (A-43).
Outcome string `json:"outcome"`
Said string `json:"said"`
}
func whyOf(why string) error {
switch {
case why == "":
return fmt.Errorf("say why: a hand act is a person's, and the why is kept with it")
case strings.ContainsAny(why, "\n\r") || len(why) > MaxWhy:
return fmt.Errorf("say why in one line of at most %d characters", MaxWhy)
}
return nil
}
// HandAct starts one act of the scheduled run by hand.
func (m *Machine) HandAct(ctx context.Context, act, by, why string) (HandAnswer, error) {
a := HandAnswer{Act: act}
if !handActs[act] {
return a, fmt.Errorf("%q is not an act of the run: snapshots, prune or apply", act)
}
if err := whyOf(why); err != nil {
return a, err
}
if m.States == nil || m.Call == nil {
return a, fmt.Errorf("this process reaches neither the module's state nor the service manager, so %s was not started", act)
}
if err := m.States.Put(StateSchedule, act, map[string]any{"by": by, "why": why, "at": m.Now().UTC()}); err != nil {
return a, fmt.Errorf("the act %s could not be written to the state %s: %v", act, StateSchedule, err)
}
if _, err := m.Call("seat:node-service-manager.start@"+m.Node, StartArgs()); err != nil {
_ = m.States.Delete(StateSchedule, act)
return a, fmt.Errorf("the service manager did not start %s: %v", RunService, err)
}
a.Outcome = "started"
a.Said = fmt.Sprintf("Started the act %s (%s) and did not wait for it to end: zfs_module_events reads its outcome.", act, RunService)
return a, nil
}
// Pause writes `paused` {by, why, at} in the state `schedule`: the run then takes and removes nothing (§24).
func (m *Machine) Pause(by, why string) error {
if err := whyOf(why); err != nil {
return err
}
if m.States == nil {
return fmt.Errorf("this process reaches no state, so the schedule was not paused")
}
return m.States.Put(StateSchedule, KeyPaused, map[string]any{"by": by, "why": why, "at": m.Now().UTC()})
}
// Resume deletes `paused`.
func (m *Machine) Resume() error {
if m.States == nil {
return fmt.Errorf("this process reaches no state, so the schedule was not resumed")
}
return m.States.Delete(StateSchedule, KeyPaused)
}
// Paused says whether the schedule is paused.
func (m *Machine) Paused() bool {
if m.States == nil {
return false
}
_, ok, err := m.States.Get(StateSchedule, KeyPaused)
return err == nil && ok
}