115 lines
4.1 KiB
Go
115 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// 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 waits at most HandWait for the act's event; past
|
|
// it, it answers "started", 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.
|
|
|
|
// HandWait is how long a hand act's tool waits for the act's event (A-12).
|
|
const HandWait = 30 * time.Second
|
|
|
|
// The acts, as the keys of the state `schedule`.
|
|
const (
|
|
ActSnapshots = "snapshots"
|
|
ActPrune = "prune"
|
|
ActApply = "apply"
|
|
)
|
|
|
|
// handEnds are the events that end each act.
|
|
var handEnds = map[string][]string{
|
|
ActSnapshots: {EventTakenPerson, EventSkippedPerson, EventRunSkipped},
|
|
ActPrune: {EventRemovedPerson, EventSkippedPerson, EventRunSkipped},
|
|
ActApply: {EventApplied, EventRefused, EventWaiting},
|
|
}
|
|
|
|
// 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 "seen" (the act's event came) or "started" (it was started, its end not seen).
|
|
Outcome string `json:"outcome"`
|
|
Event map[string]any `json:"event,omitempty"`
|
|
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 _, ok := handEnds[act]; !ok {
|
|
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)
|
|
}
|
|
if m.Await != nil {
|
|
if ev, ok := m.Await(handEnds[act], HandWait); ok {
|
|
a.Outcome, a.Event = "seen", ev
|
|
a.Said = fmt.Sprintf("The act %s ran: %v.", act, ev)
|
|
return a, nil
|
|
}
|
|
}
|
|
a.Outcome = "started"
|
|
a.Said = fmt.Sprintf("Started the act %s (%s) and did not see it end within %s: zfs_module_events reads its outcome.", act, RunService, HandWait)
|
|
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
|
|
}
|