Example #1
0
func (s *RunHookSuite) TestRunHook(c *gc.C) {
	for i, t := range runHookTests {
		c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm)
		ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged})
		c.Assert(err, jc.ErrorIsNil)

		paths := runnertesting.NewRealPaths(c)
		rnr := runner.NewRunner(ctx, paths)
		var hookExists bool
		if t.spec.perm != 0 {
			spec := t.spec
			spec.dir = "hooks"
			spec.name = hookName
			c.Logf("makeCharm %#v", spec)
			makeCharm(c, spec, paths.GetCharmDir())
			hookExists = true
		}
		t0 := time.Now()
		err = rnr.RunHook("something-happened")
		if t.err == "" && hookExists {
			c.Assert(err, jc.ErrorIsNil)
		} else if !hookExists {
			c.Assert(context.IsMissingHookError(err), jc.IsTrue)
		} else {
			c.Assert(err, gc.ErrorMatches, t.err)
		}
		if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second {
			c.Errorf("background process holding up hook execution")
		}
	}
}
Example #2
0
func (w *activeStatusWorker) runHook(code, info string) (runErr error) {
	unitTag := w.tag
	paths := uniter.NewPaths(w.config.DataDir(), unitTag)
	ctx := NewLimitedContext(unitTag.String())
	ctx.SetEnvVars(map[string]string{
		"JUJU_METER_STATUS": code,
		"JUJU_METER_INFO":   info,
	})
	r := newRunner(ctx, paths)
	unlock, err := w.acquireExecutionLock()
	if err != nil {
		return errors.Annotate(err, "failed to acquire machine lock")
	}
	defer func() {
		unlockErr := unlock()
		if unlockErr != nil {
			logger.Criticalf("hook run resulted in error %v; error overridden by unlock failure error", runErr)
			runErr = unlockErr
		}
	}()
	err = r.RunHook(string(hooks.MeterStatusChanged))
	cause := errors.Cause(err)
	switch {
	case context.IsMissingHookError(cause):
		logger.Infof("skipped %q hook (missing)", string(hooks.MeterStatusChanged))
		err = nil
	case err != nil:
		logger.Errorf("meter status worker encountered hook error: %v", err)
		return nil
	}
	return errors.Trace(w.stateFile.Write(code, info))
}
Example #3
0
func (w *connectedStatusHandler) applyStatus(code, info string, abort <-chan struct{}) {
	logger.Tracef("applying meter status change: %q (%q)", code, info)
	err := w.config.Runner.RunHook(code, info, abort)
	cause := errors.Cause(err)
	switch {
	case context.IsMissingHookError(cause):
		logger.Infof("skipped %q hook (missing)", string(hooks.MeterStatusChanged))
	case err != nil:
		logger.Errorf("meter status worker encountered hook error: %v", err)
	}
}
Example #4
0
// Execute runs the hook.
// Execute is part of the Operation interface.
func (rh *runHook) Execute(state State) (*State, error) {
	message := RunningHookMessage(rh.name)
	if err := rh.beforeHook(); err != nil {
		return nil, err
	}
	if err := rh.callbacks.SetExecutingStatus(message); err != nil {
		return nil, err
	}
	// The before hook may have updated unit status and we don't want that
	// to count so reset it here before running the hook.
	rh.runner.Context().ResetExecutionSetUnitStatus()

	ranHook := true
	step := Done

	err := rh.runner.RunHook(rh.name)
	cause := errors.Cause(err)
	switch {
	case context.IsMissingHookError(cause):
		ranHook = false
		err = nil
	case cause == context.ErrRequeueAndReboot:
		step = Queued
		fallthrough
	case cause == context.ErrReboot:
		err = ErrNeedsReboot
	case err == nil:
	default:
		logger.Errorf("hook %q failed: %v", rh.name, err)
		rh.callbacks.NotifyHookFailed(rh.name, rh.runner.Context())
		return nil, ErrHookFailed
	}

	if ranHook {
		logger.Infof("ran %q hook", rh.name)
		rh.callbacks.NotifyHookCompleted(rh.name, rh.runner.Context())
	} else {
		logger.Infof("skipped %q hook (missing)", rh.name)
	}

	var hasRunStatusSet bool
	var afterHookErr error
	if hasRunStatusSet, afterHookErr = rh.afterHook(state); afterHookErr != nil {
		return nil, afterHookErr
	}
	return stateChange{
		Kind:            RunHook,
		Step:            step,
		Hook:            &rh.info,
		HasRunStatusSet: hasRunStatusSet,
	}.apply(state), err
}
Example #5
0
// searchHook will search, in order, hooks suffixed with extensions
// in windowsSuffixOrder. As windows cares about extensions to determine
// how to execute a file, we will allow several suffixes, with powershell
// being default.
func searchHook(charmDir, hook string) (string, error) {
	hookFile := filepath.Join(charmDir, hook)
	if jujuos.HostOS() != jujuos.Windows {
		// we are not running on windows,
		// there is no need to look for suffixed hooks
		return lookPath(hookFile)
	}
	for _, suffix := range windowsSuffixOrder {
		file := fmt.Sprintf("%s%s", hookFile, suffix)
		foundHook, err := lookPath(file)
		if err != nil {
			if context.IsMissingHookError(err) {
				// look for next suffix
				continue
			}
			return "", err
		}
		return foundHook, nil
	}
	return "", context.NewMissingHookError(hook)
}
Example #6
0
func (runner *runner) runCharmHook(hookName string, env []string, charmLocation string) error {
	charmDir := runner.paths.GetCharmDir()
	hook, err := searchHook(charmDir, filepath.Join(charmLocation, hookName))
	if err != nil {
		if context.IsMissingHookError(err) {
			// Missing hook is perfectly valid, but worth mentioning.
			logger.Infof("skipped %q hook (not implemented)", hookName)
		}
		return err
	}
	hookCmd := hookCommand(hook)
	ps := exec.Command(hookCmd[0], hookCmd[1:]...)
	ps.Env = env
	ps.Dir = charmDir
	outReader, outWriter, err := os.Pipe()
	if err != nil {
		return errors.Errorf("cannot make logging pipe: %v", err)
	}
	ps.Stdout = outWriter
	ps.Stderr = outWriter
	hookLogger := &hookLogger{
		r:      outReader,
		done:   make(chan struct{}),
		logger: runner.getLogger(hookName),
	}
	go hookLogger.run()
	err = ps.Start()
	outWriter.Close()
	if err == nil {
		// Record the *os.Process of the hook
		runner.context.SetProcess(ps.Process)
		// Block until execution finishes
		err = ps.Wait()
	}
	hookLogger.stop()
	return errors.Trace(err)
}