github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/jujud/agent/unit.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agent
     5  
     6  import (
     7  	"runtime"
     8  	"time"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/gnuflag"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/utils/featureflag"
    15  	"github.com/juju/utils/voyeur"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/natefinch/lumberjack.v2"
    18  	"gopkg.in/tomb.v1"
    19  
    20  	"github.com/juju/juju/agent"
    21  	"github.com/juju/juju/api/base"
    22  	"github.com/juju/juju/api/uniter"
    23  	"github.com/juju/juju/cmd/jujud/agent/unit"
    24  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    25  	jujuversion "github.com/juju/juju/version"
    26  	"github.com/juju/juju/worker"
    27  	"github.com/juju/juju/worker/dependency"
    28  	"github.com/juju/juju/worker/introspection"
    29  	"github.com/juju/juju/worker/logsender"
    30  )
    31  
    32  var (
    33  	agentLogger = loggo.GetLogger("juju.jujud")
    34  
    35  	// should be an explicit dependency, can't do it cleanly yet
    36  	unitManifolds = unit.Manifolds
    37  )
    38  
    39  // UnitAgent is a cmd.Command responsible for running a unit agent.
    40  type UnitAgent struct {
    41  	cmd.CommandBase
    42  	tomb tomb.Tomb
    43  	AgentConf
    44  	configChangedVal *voyeur.Value
    45  	UnitName         string
    46  	runner           worker.Runner
    47  	bufferedLogs     logsender.LogRecordCh
    48  	setupLogging     func(agent.Config) error
    49  	logToStdErr      bool
    50  	ctx              *cmd.Context
    51  
    52  	// Used to signal that the upgrade worker will not
    53  	// reboot the agent on startup because there are no
    54  	// longer any immediately pending agent upgrades.
    55  	// Channel used as a selectable bool (closed means true).
    56  	initialUpgradeCheckComplete chan struct{}
    57  }
    58  
    59  // NewUnitAgent creates a new UnitAgent value properly initialized.
    60  func NewUnitAgent(ctx *cmd.Context, bufferedLogs logsender.LogRecordCh) *UnitAgent {
    61  	return &UnitAgent{
    62  		AgentConf:        NewAgentConf(""),
    63  		configChangedVal: voyeur.NewValue(true),
    64  		ctx:              ctx,
    65  		initialUpgradeCheckComplete: make(chan struct{}),
    66  		bufferedLogs:                bufferedLogs,
    67  	}
    68  }
    69  
    70  // Info returns usage information for the command.
    71  func (a *UnitAgent) Info() *cmd.Info {
    72  	return &cmd.Info{
    73  		Name:    "unit",
    74  		Purpose: "run a juju unit agent",
    75  	}
    76  }
    77  
    78  func (a *UnitAgent) SetFlags(f *gnuflag.FlagSet) {
    79  	a.AgentConf.AddFlags(f)
    80  	f.StringVar(&a.UnitName, "unit-name", "", "name of the unit to run")
    81  	f.BoolVar(&a.logToStdErr, "log-to-stderr", false, "whether to log to standard error instead of log files")
    82  }
    83  
    84  // Init initializes the command for running.
    85  func (a *UnitAgent) Init(args []string) error {
    86  	if a.UnitName == "" {
    87  		return cmdutil.RequiredError("unit-name")
    88  	}
    89  	if !names.IsValidUnit(a.UnitName) {
    90  		return errors.Errorf(`--unit-name option expects "<service>/<n>" argument`)
    91  	}
    92  	if err := a.AgentConf.CheckArgs(args); err != nil {
    93  		return err
    94  	}
    95  	a.runner = worker.NewRunner(cmdutil.IsFatal, cmdutil.MoreImportant, worker.RestartDelay)
    96  
    97  	if !a.logToStdErr {
    98  		if err := a.ReadConfig(a.Tag().String()); err != nil {
    99  			return err
   100  		}
   101  		agentConfig := a.CurrentConfig()
   102  
   103  		// the writer in ctx.stderr gets set as the loggo writer in github.com/juju/cmd/logging.go
   104  		a.ctx.Stderr = &lumberjack.Logger{
   105  			Filename:   agent.LogFilename(agentConfig),
   106  			MaxSize:    300, // megabytes
   107  			MaxBackups: 2,
   108  		}
   109  
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // Stop stops the unit agent.
   116  func (a *UnitAgent) Stop() error {
   117  	a.runner.Kill()
   118  	return a.tomb.Wait()
   119  }
   120  
   121  // Run runs a unit agent.
   122  func (a *UnitAgent) Run(ctx *cmd.Context) error {
   123  	defer a.tomb.Done()
   124  	if err := a.ReadConfig(a.Tag().String()); err != nil {
   125  		return err
   126  	}
   127  	agentLogger.Infof("unit agent %v start (%s [%s])", a.Tag().String(), jujuversion.Current, runtime.Compiler)
   128  	if flags := featureflag.String(); flags != "" {
   129  		logger.Warningf("developer feature flags enabled: %s", flags)
   130  	}
   131  
   132  	a.runner.StartWorker("api", a.APIWorkers)
   133  	err := cmdutil.AgentDone(logger, a.runner.Wait())
   134  	a.tomb.Kill(err)
   135  	return err
   136  }
   137  
   138  // APIWorkers returns a dependency.Engine running the unit agent's responsibilities.
   139  func (a *UnitAgent) APIWorkers() (worker.Worker, error) {
   140  	manifolds := unitManifolds(unit.ManifoldsConfig{
   141  		Agent:               agent.APIHostPortsSetter{a},
   142  		LogSource:           a.bufferedLogs,
   143  		LeadershipGuarantee: 30 * time.Second,
   144  		AgentConfigChanged:  a.configChangedVal,
   145  		ValidateMigration:   a.validateMigration,
   146  	})
   147  
   148  	config := dependency.EngineConfig{
   149  		IsFatal:     cmdutil.IsFatal,
   150  		WorstError:  cmdutil.MoreImportantError,
   151  		ErrorDelay:  3 * time.Second,
   152  		BounceDelay: 10 * time.Millisecond,
   153  	}
   154  	engine, err := dependency.NewEngine(config)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	if err := dependency.Install(engine, manifolds); err != nil {
   159  		if err := worker.Stop(engine); err != nil {
   160  			logger.Errorf("while stopping engine with bad manifolds: %v", err)
   161  		}
   162  		return nil, err
   163  	}
   164  	if err := startIntrospection(introspectionConfig{
   165  		Agent:      a,
   166  		Engine:     engine,
   167  		WorkerFunc: introspection.NewWorker,
   168  	}); err != nil {
   169  		// If the introspection worker failed to start, we just log error
   170  		// but continue. It is very unlikely to happen in the real world
   171  		// as the only issue is connecting to the abstract domain socket
   172  		// and the agent is controlled by by the OS to only have one.
   173  		logger.Errorf("failed to start introspection worker: %v", err)
   174  	}
   175  	return engine, nil
   176  }
   177  
   178  func (a *UnitAgent) Tag() names.Tag {
   179  	return names.NewUnitTag(a.UnitName)
   180  }
   181  
   182  func (a *UnitAgent) ChangeConfig(mutate agent.ConfigMutator) error {
   183  	err := a.AgentConf.ChangeConfig(mutate)
   184  	a.configChangedVal.Set(true)
   185  	return errors.Trace(err)
   186  }
   187  
   188  // validateMigration is called by the migrationminion to help check
   189  // that the agent will be ok when connected to a new controller.
   190  func (a *UnitAgent) validateMigration(apiCaller base.APICaller) error {
   191  	// TODO(mjs) - more extensive checks to come.
   192  	unitTag := names.NewUnitTag(a.UnitName)
   193  	facade := uniter.NewState(apiCaller, unitTag)
   194  	_, err := facade.Unit(unitTag)
   195  	if err != nil {
   196  		return errors.Trace(err)
   197  	}
   198  	model, err := facade.Model()
   199  	if err != nil {
   200  		return errors.Trace(err)
   201  	}
   202  	curModelUUID := a.CurrentConfig().Model().Id()
   203  	newModelUUID := model.UUID()
   204  	if newModelUUID != curModelUUID {
   205  		return errors.Errorf("model mismatch when validating: got %q, expected %q",
   206  			newModelUUID, curModelUUID)
   207  	}
   208  	return nil
   209  }