github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"time"
     8  
     9  	"github.com/juju/clock"
    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/voyeur"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/juju/worker.v1"
    18  	"gopkg.in/juju/worker.v1/dependency"
    19  	"gopkg.in/natefinch/lumberjack.v2"
    20  
    21  	"github.com/juju/juju/agent"
    22  	"github.com/juju/juju/api/base"
    23  	"github.com/juju/juju/api/uniter"
    24  	jujucmd "github.com/juju/juju/cmd"
    25  	"github.com/juju/juju/cmd/jujud/agent/unit"
    26  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    27  	"github.com/juju/juju/core/machinelock"
    28  	"github.com/juju/juju/upgrades"
    29  	jworker "github.com/juju/juju/worker"
    30  	"github.com/juju/juju/worker/gate"
    31  	"github.com/juju/juju/worker/introspection"
    32  	"github.com/juju/juju/worker/logsender"
    33  	"github.com/juju/juju/worker/upgradesteps"
    34  )
    35  
    36  var (
    37  	// should be an explicit dependency, can't do it cleanly yet
    38  	unitManifolds = unit.Manifolds
    39  )
    40  
    41  // UnitAgent is a cmd.Command responsible for running a unit agent.
    42  type UnitAgent struct {
    43  	cmd.CommandBase
    44  	AgentConf
    45  	configChangedVal *voyeur.Value
    46  	UnitName         string
    47  	runner           *worker.Runner
    48  	bufferedLogger   *logsender.BufferedLogWriter
    49  	setupLogging     func(agent.Config) error
    50  	logToStdErr      bool
    51  	ctx              *cmd.Context
    52  	dead             chan struct{}
    53  	errReason        error
    54  
    55  	// Used to signal that the upgrade worker will not
    56  	// reboot the agent on startup because there are no
    57  	// longer any immediately pending agent upgrades.
    58  	initialUpgradeCheckComplete gate.Lock
    59  	preUpgradeSteps             upgrades.PreUpgradeStepsFunc
    60  	upgradeComplete             gate.Lock
    61  
    62  	prometheusRegistry *prometheus.Registry
    63  }
    64  
    65  // NewUnitAgent creates a new UnitAgent value properly initialized.
    66  func NewUnitAgent(ctx *cmd.Context, bufferedLogger *logsender.BufferedLogWriter) (*UnitAgent, error) {
    67  	prometheusRegistry, err := newPrometheusRegistry()
    68  	if err != nil {
    69  		return nil, errors.Trace(err)
    70  	}
    71  	return &UnitAgent{
    72  		AgentConf:                   NewAgentConf(""),
    73  		configChangedVal:            voyeur.NewValue(true),
    74  		ctx:                         ctx,
    75  		dead:                        make(chan struct{}),
    76  		initialUpgradeCheckComplete: gate.NewLock(),
    77  		bufferedLogger:              bufferedLogger,
    78  		prometheusRegistry:          prometheusRegistry,
    79  		preUpgradeSteps:             upgrades.PreUpgradeSteps,
    80  	}, nil
    81  }
    82  
    83  // Info returns usage information for the command.
    84  func (a *UnitAgent) Info() *cmd.Info {
    85  	return jujucmd.Info(&cmd.Info{
    86  		Name:    "unit",
    87  		Purpose: "run a juju unit agent",
    88  	})
    89  }
    90  
    91  func (a *UnitAgent) SetFlags(f *gnuflag.FlagSet) {
    92  	a.AgentConf.AddFlags(f)
    93  	f.StringVar(&a.UnitName, "unit-name", "", "name of the unit to run")
    94  	f.BoolVar(&a.logToStdErr, "log-to-stderr", false, "whether to log to standard error instead of log files")
    95  }
    96  
    97  // Init initializes the command for running.
    98  func (a *UnitAgent) Init(args []string) error {
    99  	if a.UnitName == "" {
   100  		return cmdutil.RequiredError("unit-name")
   101  	}
   102  	if !names.IsValidUnit(a.UnitName) {
   103  		return errors.Errorf(`--unit-name option expects "<application>/<n>" argument`)
   104  	}
   105  	if err := a.AgentConf.CheckArgs(args); err != nil {
   106  		return err
   107  	}
   108  	a.runner = worker.NewRunner(worker.RunnerParams{
   109  		IsFatal:       cmdutil.IsFatal,
   110  		MoreImportant: cmdutil.MoreImportant,
   111  		RestartDelay:  jworker.RestartDelay,
   112  	})
   113  
   114  	if err := a.ReadConfig(a.Tag().String()); err != nil {
   115  		return err
   116  	}
   117  	agentConfig := a.CurrentConfig()
   118  
   119  	if !a.logToStdErr {
   120  
   121  		// the writer in ctx.stderr gets set as the loggo writer in github.com/juju/cmd/logging.go
   122  		a.ctx.Stderr = &lumberjack.Logger{
   123  			Filename:   agent.LogFilename(agentConfig),
   124  			MaxSize:    300, // megabytes
   125  			MaxBackups: 2,
   126  			Compress:   true,
   127  		}
   128  
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // Stop stops the unit agent.
   135  func (a *UnitAgent) Stop() error {
   136  	a.runner.Kill()
   137  	return a.Wait()
   138  }
   139  
   140  // Wait waits for the unit agent to finish
   141  func (a *UnitAgent) Wait() error {
   142  	<-a.dead
   143  	return a.errReason
   144  }
   145  
   146  // Done signals the unit agent is finished
   147  func (a *UnitAgent) Done(err error) {
   148  	a.errReason = err
   149  	close(a.dead)
   150  }
   151  
   152  func (a *UnitAgent) isUpgradeRunning() bool {
   153  	return !a.upgradeComplete.IsUnlocked()
   154  }
   155  
   156  func (a *UnitAgent) isInitialUpgradeCheckPending() bool {
   157  	return !a.initialUpgradeCheckComplete.IsUnlocked()
   158  }
   159  
   160  // Run runs a unit agent.
   161  func (a *UnitAgent) Run(ctx *cmd.Context) (err error) {
   162  	defer a.Done(err)
   163  	if err := a.ReadConfig(a.Tag().String()); err != nil {
   164  		return err
   165  	}
   166  	setupAgentLogging(a.CurrentConfig())
   167  
   168  	a.runner.StartWorker("api", a.APIWorkers)
   169  	err = cmdutil.AgentDone(logger, a.runner.Wait())
   170  	return err
   171  }
   172  
   173  // APIWorkers returns a dependency.Engine running the unit agent's responsibilities.
   174  func (a *UnitAgent) APIWorkers() (worker.Worker, error) {
   175  	updateAgentConfLogging := func(loggingConfig string) error {
   176  		return a.AgentConf.ChangeConfig(func(setter agent.ConfigSetter) error {
   177  			setter.SetLoggingConfig(loggingConfig)
   178  			return nil
   179  		})
   180  	}
   181  
   182  	agentConfig := a.AgentConf.CurrentConfig()
   183  	a.upgradeComplete = upgradesteps.NewLock(agentConfig)
   184  	machineLock, err := machinelock.New(machinelock.Config{
   185  		AgentName:   a.Tag().String(),
   186  		Clock:       clock.WallClock,
   187  		Logger:      loggo.GetLogger("juju.machinelock"),
   188  		LogFilename: agent.MachineLockLogFilename(agentConfig),
   189  	})
   190  	// There will only be an error if the required configuration
   191  	// values are not passed in.
   192  	if err != nil {
   193  		return nil, errors.Trace(err)
   194  	}
   195  
   196  	manifolds := unitManifolds(unit.ManifoldsConfig{
   197  		Agent:                agent.APIHostPortsSetter{a},
   198  		LogSource:            a.bufferedLogger.Logs(),
   199  		LeadershipGuarantee:  30 * time.Second,
   200  		AgentConfigChanged:   a.configChangedVal,
   201  		ValidateMigration:    a.validateMigration,
   202  		PrometheusRegisterer: a.prometheusRegistry,
   203  		UpdateLoggerConfig:   updateAgentConfLogging,
   204  		PreviousAgentVersion: agentConfig.UpgradedToVersion(),
   205  		PreUpgradeSteps:      a.preUpgradeSteps,
   206  		UpgradeStepsLock:     a.upgradeComplete,
   207  		UpgradeCheckLock:     a.initialUpgradeCheckComplete,
   208  		MachineLock:          machineLock,
   209  	})
   210  
   211  	engine, err := dependency.NewEngine(dependencyEngineConfig())
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	if err := dependency.Install(engine, manifolds); err != nil {
   216  		if err := worker.Stop(engine); err != nil {
   217  			logger.Errorf("while stopping engine with bad manifolds: %v", err)
   218  		}
   219  		return nil, err
   220  	}
   221  	if err := startIntrospection(introspectionConfig{
   222  		Agent:              a,
   223  		Engine:             engine,
   224  		NewSocketName:      DefaultIntrospectionSocketName,
   225  		PrometheusGatherer: a.prometheusRegistry,
   226  		MachineLock:        machineLock,
   227  		WorkerFunc:         introspection.NewWorker,
   228  	}); err != nil {
   229  		// If the introspection worker failed to start, we just log error
   230  		// but continue. It is very unlikely to happen in the real world
   231  		// as the only issue is connecting to the abstract domain socket
   232  		// and the agent is controlled by by the OS to only have one.
   233  		logger.Errorf("failed to start introspection worker: %v", err)
   234  	}
   235  	return engine, nil
   236  }
   237  
   238  func (a *UnitAgent) Tag() names.Tag {
   239  	return names.NewUnitTag(a.UnitName)
   240  }
   241  
   242  func (a *UnitAgent) ChangeConfig(mutate agent.ConfigMutator) error {
   243  	err := a.AgentConf.ChangeConfig(mutate)
   244  	a.configChangedVal.Set(true)
   245  	return errors.Trace(err)
   246  }
   247  
   248  // validateMigration is called by the migrationminion to help check
   249  // that the agent will be ok when connected to a new controller.
   250  func (a *UnitAgent) validateMigration(apiCaller base.APICaller) error {
   251  	// TODO(mjs) - more extensive checks to come.
   252  	unitTag := names.NewUnitTag(a.UnitName)
   253  	facade := uniter.NewState(apiCaller, unitTag)
   254  	_, err := facade.Unit(unitTag)
   255  	if err != nil {
   256  		return errors.Trace(err)
   257  	}
   258  	model, err := facade.Model()
   259  	if err != nil {
   260  		return errors.Trace(err)
   261  	}
   262  	curModelUUID := a.CurrentConfig().Model().Id()
   263  	newModelUUID := model.UUID
   264  	if newModelUUID != curModelUUID {
   265  		return errors.Errorf("model mismatch when validating: got %q, expected %q",
   266  			newModelUUID, curModelUUID)
   267  	}
   268  	return nil
   269  }