Skip to content

Commit

Permalink
Add dry run on daemon and managers
Browse files Browse the repository at this point in the history
  • Loading branch information
anpep committed Jun 8, 2023
1 parent 0c64c21 commit 8ecc565
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 38 deletions.
6 changes: 6 additions & 0 deletions internals/cli/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type sharedRunEnterOpts struct {
Hold bool `long:"hold"`
HTTP string `long:"http"`
Verbose bool `short:"v" long:"verbose"`
Dry bool `long:"dry"`
Args [][]string `long:"args" terminator:";"`
}

Expand All @@ -56,6 +57,7 @@ var sharedRunEnterOptsHelp = map[string]string{
"hold": "Do not start default services automatically",
"http": `Start HTTP API listening on this address (e.g., ":4000")`,
"verbose": "Log all output from services to stdout",
"dry": `Attempt to run without actually running`,
"args": `Provide additional arguments to a service`,
}

Expand Down Expand Up @@ -144,6 +146,7 @@ func runDaemon(rcmd *cmdRun, ch chan os.Signal, ready chan<- func()) error {
dopts := daemon.Options{
Dir: pebbleDir,
SocketPath: socketPath,
Dry: rcmd.Dry,
}
if rcmd.Verbose {
dopts.ServiceOutput = os.Stdout
Expand All @@ -154,6 +157,9 @@ func runDaemon(rcmd *cmdRun, ch chan os.Signal, ready chan<- func()) error {
if err != nil {
return err
}
if rcmd.Dry {
return nil
}
if err := d.Init(); err != nil {
return err
}
Expand Down
5 changes: 4 additions & 1 deletion internals/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type Options struct {
// ServiceOuput is an optional io.Writer for the service log output, if set, all services
// log output will be written to the writer.
ServiceOutput io.Writer

// Dry will only perform initialization tasks that don't have system-wide side effects.
Dry bool
}

// A Daemon listens for requests and routes them to the right command
Expand Down Expand Up @@ -790,7 +793,7 @@ func New(opts *Options) (*Daemon, error) {
httpAddress: opts.HTTPAddress,
}

ovld, err := overlord.New(opts.Dir, d, opts.ServiceOutput)
ovld, err := overlord.New(opts.Dir, d, opts.ServiceOutput, opts.Dry)
if err == errExpectedReboot {
// we proceed without overlord until we reach Stop
// where we will schedule and wait again for a system restart.
Expand Down
12 changes: 12 additions & 0 deletions internals/overlord/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,15 @@ func (osb *overlordStateBackend) Checkpoint(data []byte) error {
func (osb *overlordStateBackend) EnsureBefore(d time.Duration) {
osb.ensureBefore(d)
}

type noopStateBackend struct {
ensureBefore func(d time.Duration)
}

func (nsb *noopStateBackend) Checkpoint(data []byte) error {
return nil
}

func (nsb *noopStateBackend) EnsureBefore(d time.Duration) {
nsb.ensureBefore(d)
}
5 changes: 5 additions & 0 deletions internals/overlord/cmdstate/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func NewManager(runner *state.TaskRunner) *CommandManager {
return manager
}

// DryStart is part of the overlord.StateManager interface.
func (m *CommandManager) DryStart() error {
return nil
}

// Ensure is part of the overlord.StateManager interface.
func (m *CommandManager) Ensure() error {
return nil
Expand Down
5 changes: 5 additions & 0 deletions internals/overlord/logstate/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func (m *LogManager) ServiceStarted(serviceName string, buffer *servicelog.RingB
// TODO: implement
}

// DryStart implements overlord.StateManager.
func (m *LogManager) DryStart() error {
return nil
}

// Ensure implements overlord.StateManager.
func (m *LogManager) Ensure() error {
return nil
Expand Down
2 changes: 1 addition & 1 deletion internals/overlord/managers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (s *mgrsSuite) SetUpTest(c *C) {

s.dir = c.MkDir()

o, err := overlord.New(s.dir, nil, nil)
o, err := overlord.New(s.dir, nil, nil, false)
c.Assert(err, IsNil)
s.o = o
}
Expand Down
27 changes: 20 additions & 7 deletions internals/overlord/overlord.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type Overlord struct {

// New creates a new Overlord with all its state managers.
// It can be provided with an optional restart.Handler.
func New(pebbleDir string, restartHandler restart.Handler, serviceOutput io.Writer) (*Overlord, error) {
func New(pebbleDir string, restartHandler restart.Handler, serviceOutput io.Writer, dry bool) (*Overlord, error) {
o := &Overlord{
pebbleDir: pebbleDir,
loopTomb: new(tomb.Tomb),
Expand All @@ -89,10 +89,18 @@ func New(pebbleDir string, restartHandler restart.Handler, serviceOutput io.Writ
}
statePath := filepath.Join(pebbleDir, ".pebble.state")

backend := &overlordStateBackend{
path: statePath,
ensureBefore: o.ensureBefore,
var backend state.Backend
if dry {
backend = &noopStateBackend{
ensureBefore: o.ensureBefore,
}
} else {
backend = &overlordStateBackend{
path: statePath,
ensureBefore: o.ensureBefore,
}
}

s, err := loadState(statePath, restartHandler, backend)
if err != nil {
return nil, err
Expand Down Expand Up @@ -133,6 +141,11 @@ func New(pebbleDir string, restartHandler restart.Handler, serviceOutput io.Writ
// the shared task runner should be added last!
o.stateEng.AddManager(o.runner)

// Dry start all managers.
if err := o.stateEng.DryStart(); err != nil {
return nil, err
}

return o, nil
}

Expand Down Expand Up @@ -318,15 +331,15 @@ func (o *Overlord) settle(timeout time.Duration, beforeCleanups func()) error {
if timeout > 0 && time.Since(t0) > timeout {
err := fmt.Errorf("Settle is not converging")
if len(errs) != 0 {
return &ensureError{append(errs, err)}
return &multiError{append(errs, err)}
}
return err
}
next := o.ensureTimerReset()
err := o.stateEng.Ensure()
switch ee := err.(type) {
case nil:
case *ensureError:
case *multiError:
errs = append(errs, ee.errs...)
default:
errs = append(errs, err)
Expand All @@ -353,7 +366,7 @@ func (o *Overlord) settle(timeout time.Duration, beforeCleanups func()) error {
}
}
if len(errs) != 0 {
return &ensureError{errs}
return &multiError{errs}
}
return nil
}
Expand Down
42 changes: 27 additions & 15 deletions internals/overlord/overlord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (ovs *overlordSuite) TestNew(c *C) {
restore := patch.Fake(42, 2, nil)
defer restore()

o, err := overlord.New(ovs.dir, nil, nil)
o, err := overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, IsNil)
c.Check(o, NotNil)

Expand All @@ -83,7 +83,7 @@ func (ovs *overlordSuite) TestNewWithGoodState(c *C) {
err := ioutil.WriteFile(ovs.statePath, fakeState, 0600)
c.Assert(err, IsNil)

o, err := overlord.New(ovs.dir, nil, nil)
o, err := overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, IsNil)

state := o.State()
Expand Down Expand Up @@ -111,7 +111,7 @@ func (ovs *overlordSuite) TestNewWithInvalidState(c *C) {
err := ioutil.WriteFile(ovs.statePath, fakeState, 0600)
c.Assert(err, IsNil)

_, err = overlord.New(ovs.dir, nil, nil)
_, err = overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, ErrorMatches, "cannot read state: EOF")
}

Expand All @@ -130,7 +130,7 @@ func (ovs *overlordSuite) TestNewWithPatches(c *C) {
err := ioutil.WriteFile(ovs.statePath, fakeState, 0600)
c.Assert(err, IsNil)

o, err := overlord.New(ovs.dir, nil, nil)
o, err := overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, IsNil)

state := o.State()
Expand Down Expand Up @@ -163,6 +163,10 @@ type witnessManager struct {
ensureCallback func(s *state.State) error
}

func (wm *witnessManager) DryStart() error {
return nil
}

func (wm *witnessManager) Ensure() error {
if wm.expectedEnsure--; wm.expectedEnsure == 0 {
close(wm.ensureCalled)
Expand All @@ -175,7 +179,7 @@ func (wm *witnessManager) Ensure() error {
}

func (ovs *overlordSuite) TestTrivialRunAndStop(c *C) {
o, err := overlord.New(ovs.dir, nil, nil)
o, err := overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, IsNil)

o.Loop()
Expand All @@ -185,7 +189,7 @@ func (ovs *overlordSuite) TestTrivialRunAndStop(c *C) {
}

func (ovs *overlordSuite) TestUnknownTasks(c *C) {
o, err := overlord.New(ovs.dir, nil, nil)
o, err := overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, IsNil)

// unknown tasks are ignored and succeed
Expand Down Expand Up @@ -486,7 +490,7 @@ func (ovs *overlordSuite) TestCheckpoint(c *C) {
oldUmask := syscall.Umask(0)
defer syscall.Umask(oldUmask)

o, err := overlord.New(ovs.dir, nil, nil)
o, err := overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, IsNil)

s := o.State()
Expand All @@ -502,7 +506,8 @@ func (ovs *overlordSuite) TestCheckpoint(c *C) {
}

type sampleManager struct {
ensureCallback func()
dryStartCallback func()
ensureCallback func()
}

func newSampleManager(s *state.State, runner *state.TaskRunner) *sampleManager {
Expand Down Expand Up @@ -554,6 +559,13 @@ func newSampleManager(s *state.State, runner *state.TaskRunner) *sampleManager {
return sm
}

func (sm *sampleManager) DryStart() error {
if sm.dryStartCallback != nil {
sm.dryStartCallback()
}
return nil
}

func (sm *sampleManager) Ensure() error {
if sm.ensureCallback != nil {
sm.ensureCallback()
Expand Down Expand Up @@ -727,7 +739,7 @@ func (ovs *overlordSuite) TestSettleExplicitEnsureBefore(c *C) {
}

func (ovs *overlordSuite) TestRequestRestartNoHandler(c *C) {
o, err := overlord.New(ovs.dir, nil, nil)
o, err := overlord.New(ovs.dir, nil, nil, false)
c.Assert(err, IsNil)

st := o.State()
Expand Down Expand Up @@ -760,7 +772,7 @@ func (rb *testRestartHandler) RebootIsMissing(_ *state.State) error {
func (ovs *overlordSuite) TestRequestRestartHandler(c *C) {
rb := &testRestartHandler{}

o, err := overlord.New(ovs.dir, rb, nil)
o, err := overlord.New(ovs.dir, rb, nil, false)
c.Assert(err, IsNil)

st := o.State()
Expand All @@ -779,7 +791,7 @@ func (ovs *overlordSuite) TestVerifyRebootNoPendingReboot(c *C) {

rb := &testRestartHandler{}

_, err = overlord.New(ovs.dir, rb, nil)
_, err = overlord.New(ovs.dir, rb, nil, false)
c.Assert(err, IsNil)

c.Check(rb.rebootState, Equals, "as-expected")
Expand All @@ -792,7 +804,7 @@ func (ovs *overlordSuite) TestVerifyRebootOK(c *C) {

rb := &testRestartHandler{}

_, err = overlord.New(ovs.dir, rb, nil)
_, err = overlord.New(ovs.dir, rb, nil, false)
c.Assert(err, IsNil)

c.Check(rb.rebootState, Equals, "as-expected")
Expand All @@ -806,7 +818,7 @@ func (ovs *overlordSuite) TestVerifyRebootOKButError(c *C) {
e := errors.New("boom")
rb := &testRestartHandler{rebootVerifiedErr: e}

_, err = overlord.New(ovs.dir, rb, nil)
_, err = overlord.New(ovs.dir, rb, nil, false)
c.Assert(err, Equals, e)

c.Check(rb.rebootState, Equals, "as-expected")
Expand All @@ -822,7 +834,7 @@ func (ovs *overlordSuite) TestVerifyRebootIsMissing(c *C) {

rb := &testRestartHandler{}

_, err = overlord.New(ovs.dir, rb, nil)
_, err = overlord.New(ovs.dir, rb, nil, false)
c.Assert(err, IsNil)

c.Check(rb.rebootState, Equals, "did-not-happen")
Expand All @@ -839,7 +851,7 @@ func (ovs *overlordSuite) TestVerifyRebootIsMissingError(c *C) {
e := errors.New("boom")
rb := &testRestartHandler{rebootVerifiedErr: e}

_, err = overlord.New(ovs.dir, rb, nil)
_, err = overlord.New(ovs.dir, rb, nil, false)
c.Assert(err, Equals, e)

c.Check(rb.rebootState, Equals, "did-not-happen")
Expand Down
6 changes: 6 additions & 0 deletions internals/overlord/servstate/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ func (m *ServiceManager) acquirePlan() (release func(), err error) {
return release, nil
}

// DryStart implements StateManager.DryStart.
func (m *ServiceManager) DryStart() error {
_, err := plan.ReadDir(m.pebbleDir)
return err
}

// Ensure implements StateManager.Ensure.
func (m *ServiceManager) Ensure() error {
return nil
Expand Down
4 changes: 4 additions & 0 deletions internals/overlord/state/taskrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,10 @@ func (r *TaskRunner) tryUndo(t *Task) {
}
}

func (r *TaskRunner) DryStart() error {
return nil
}

// Ensure starts new goroutines for all known tasks with no pending
// dependencies.
// Note that Ensure will lock the state.
Expand Down
Loading

0 comments on commit 8ecc565

Please sign in to comment.