github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/jujud/agent/caasoperator.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agent
     5  
     6  import (
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"time"
    12  
    13  	"github.com/juju/clock"
    14  	"github.com/juju/cmd"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/gnuflag"
    17  	"github.com/juju/loggo"
    18  	"github.com/juju/utils/featureflag"
    19  	"github.com/juju/utils/voyeur"
    20  	"github.com/prometheus/client_golang/prometheus"
    21  	"gopkg.in/juju/names.v2"
    22  	"gopkg.in/juju/worker.v1"
    23  	"gopkg.in/juju/worker.v1/dependency"
    24  
    25  	"github.com/juju/juju/agent"
    26  	"github.com/juju/juju/api/base"
    27  	apicaasoperator "github.com/juju/juju/api/caasoperator"
    28  	jujucmd "github.com/juju/juju/cmd"
    29  	"github.com/juju/juju/cmd/jujud/agent/caasoperator"
    30  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    31  	"github.com/juju/juju/core/machinelock"
    32  	jujuversion "github.com/juju/juju/version"
    33  	jworker "github.com/juju/juju/worker"
    34  	"github.com/juju/juju/worker/gate"
    35  	"github.com/juju/juju/worker/introspection"
    36  	"github.com/juju/juju/worker/logsender"
    37  	"github.com/juju/juju/worker/upgradesteps"
    38  )
    39  
    40  var (
    41  	// Should be an explicit dependency, can't do it cleanly yet.
    42  	// Exported for testing.
    43  	CaasOperatorManifolds = caasoperator.Manifolds
    44  )
    45  
    46  // CaasOperatorAgent is a cmd.Command responsible for running a CAAS operator agent.
    47  type CaasOperatorAgent struct {
    48  	cmd.CommandBase
    49  	AgentConf
    50  	configChangedVal *voyeur.Value
    51  	ApplicationName  string
    52  	runner           *worker.Runner
    53  	bufferedLogger   *logsender.BufferedLogWriter
    54  	setupLogging     func(agent.Config) error
    55  	ctx              *cmd.Context
    56  	dead             chan struct{}
    57  	errReason        error
    58  	machineLock      machinelock.Lock
    59  
    60  	upgradeComplete gate.Lock
    61  
    62  	prometheusRegistry *prometheus.Registry
    63  }
    64  
    65  // NewCaasOperatorAgent creates a new CAASOperatorAgent instance properly initialized.
    66  func NewCaasOperatorAgent(ctx *cmd.Context, bufferedLogger *logsender.BufferedLogWriter) (*CaasOperatorAgent, error) {
    67  	prometheusRegistry, err := newPrometheusRegistry()
    68  	if err != nil {
    69  		return nil, errors.Trace(err)
    70  	}
    71  	return &CaasOperatorAgent{
    72  		AgentConf:          NewAgentConf(""),
    73  		configChangedVal:   voyeur.NewValue(true),
    74  		ctx:                ctx,
    75  		dead:               make(chan struct{}),
    76  		bufferedLogger:     bufferedLogger,
    77  		prometheusRegistry: prometheusRegistry,
    78  	}, nil
    79  }
    80  
    81  // Info implements Command.
    82  func (op *CaasOperatorAgent) Info() *cmd.Info {
    83  	return jujucmd.Info(&cmd.Info{
    84  		Name:    "caasoperator",
    85  		Purpose: "run a juju CAAS Operator",
    86  	})
    87  }
    88  
    89  // SetFlags implements Command.
    90  func (op *CaasOperatorAgent) SetFlags(f *gnuflag.FlagSet) {
    91  	op.AgentConf.AddFlags(f)
    92  	f.StringVar(&op.ApplicationName, "application-name", "", "name of the application")
    93  }
    94  
    95  // Init initializes the command for running.
    96  func (op *CaasOperatorAgent) Init(args []string) error {
    97  	if op.ApplicationName == "" {
    98  		return cmdutil.RequiredError("application-name")
    99  	}
   100  	if !names.IsValidApplication(op.ApplicationName) {
   101  		return errors.Errorf(`--application-name option expects "<application>" argument`)
   102  	}
   103  	if err := op.AgentConf.CheckArgs(args); err != nil {
   104  		return err
   105  	}
   106  	op.runner = worker.NewRunner(worker.RunnerParams{
   107  		IsFatal:       cmdutil.IsFatal,
   108  		MoreImportant: cmdutil.MoreImportant,
   109  		RestartDelay:  jworker.RestartDelay,
   110  	})
   111  	return nil
   112  }
   113  
   114  // Wait waits for the CaasOperator agent to finish.
   115  func (op *CaasOperatorAgent) Wait() error {
   116  	<-op.dead
   117  	return op.errReason
   118  }
   119  
   120  // Stop implements Worker.
   121  func (op *CaasOperatorAgent) Stop() error {
   122  	op.runner.Kill()
   123  	return op.Wait()
   124  }
   125  
   126  // Done signals the machine agent is finished
   127  func (op *CaasOperatorAgent) Done(err error) {
   128  	op.errReason = err
   129  	close(op.dead)
   130  }
   131  
   132  // maybeCopyAgentConfig copies the read-only agent config template
   133  // to the writeable agent config file if the file doesn't yet exist.
   134  func (op *CaasOperatorAgent) maybeCopyAgentConfig() error {
   135  	err := op.ReadConfig(op.Tag().String())
   136  	if err == nil {
   137  		return nil
   138  	}
   139  	if !os.IsNotExist(errors.Cause(err)) {
   140  		logger.Errorf("reading initial agent config file: %v", err)
   141  		return errors.Trace(err)
   142  	}
   143  	templateFile := filepath.Join(agent.Dir(op.DataDir(), op.Tag()), "template-agent.conf")
   144  	if err := copyFile(agent.ConfigPath(op.DataDir(), op.Tag()), templateFile); err != nil {
   145  		logger.Errorf("copying agent config file template: %v", err)
   146  		return errors.Trace(err)
   147  	}
   148  	return op.ReadConfig(op.Tag().String())
   149  }
   150  
   151  func copyFile(dest, source string) error {
   152  	df, err := os.OpenFile(dest, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600)
   153  	if err != nil {
   154  		return errors.Trace(err)
   155  	}
   156  	defer df.Close()
   157  
   158  	f, err := os.Open(source)
   159  	if err != nil {
   160  		return errors.Trace(err)
   161  	}
   162  	defer f.Close()
   163  
   164  	_, err = io.Copy(df, f)
   165  	return errors.Trace(err)
   166  }
   167  
   168  // Run implements Command.
   169  func (op *CaasOperatorAgent) Run(ctx *cmd.Context) (err error) {
   170  	defer op.Done(err)
   171  	if err := op.maybeCopyAgentConfig(); err != nil {
   172  		return errors.Annotate(err, "creating agent config from template")
   173  	}
   174  	agentConfig := op.CurrentConfig()
   175  	machineLock, err := machinelock.New(machinelock.Config{
   176  		AgentName:   op.Tag().String(),
   177  		Clock:       clock.WallClock,
   178  		Logger:      loggo.GetLogger("juju.machinelock"),
   179  		LogFilename: agent.MachineLockLogFilename(agentConfig),
   180  	})
   181  	// There will only be an error if the required configuration
   182  	// values are not passed in.
   183  	if err != nil {
   184  		return errors.Trace(err)
   185  	}
   186  	op.machineLock = machineLock
   187  	op.upgradeComplete = upgradesteps.NewLock(agentConfig)
   188  
   189  	logger.Infof("caas operator %v start (%s [%s])", op.Tag().String(), jujuversion.Current, runtime.Compiler)
   190  	if flags := featureflag.String(); flags != "" {
   191  		logger.Warningf("developer feature flags enabled: %s", flags)
   192  	}
   193  
   194  	op.runner.StartWorker("api", op.Workers)
   195  	return cmdutil.AgentDone(logger, op.runner.Wait())
   196  }
   197  
   198  // Workers returns a dependency.Engine running the operator's responsibilities.
   199  func (op *CaasOperatorAgent) Workers() (worker.Worker, error) {
   200  	updateAgentConfLogging := func(loggingConfig string) error {
   201  		return op.AgentConf.ChangeConfig(func(setter agent.ConfigSetter) error {
   202  			setter.SetLoggingConfig(loggingConfig)
   203  			return nil
   204  		})
   205  	}
   206  
   207  	manifolds := CaasOperatorManifolds(caasoperator.ManifoldsConfig{
   208  		Agent:                agent.APIHostPortsSetter{op},
   209  		AgentConfigChanged:   op.configChangedVal,
   210  		Clock:                clock.WallClock,
   211  		LogSource:            op.bufferedLogger.Logs(),
   212  		UpdateLoggerConfig:   updateAgentConfLogging,
   213  		PrometheusRegisterer: op.prometheusRegistry,
   214  		LeadershipGuarantee:  30 * time.Second,
   215  		UpgradeStepsLock:     op.upgradeComplete,
   216  		ValidateMigration:    op.validateMigration,
   217  		MachineLock:          op.machineLock,
   218  	})
   219  
   220  	engine, err := dependency.NewEngine(dependencyEngineConfig())
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	if err := dependency.Install(engine, manifolds); err != nil {
   225  		if err := worker.Stop(engine); err != nil {
   226  			logger.Errorf("while stopping engine with bad manifolds: %v", err)
   227  		}
   228  		return nil, err
   229  	}
   230  	if err := startIntrospection(introspectionConfig{
   231  		Agent:              op,
   232  		Engine:             engine,
   233  		MachineLock:        op.machineLock,
   234  		NewSocketName:      DefaultIntrospectionSocketName,
   235  		PrometheusGatherer: op.prometheusRegistry,
   236  		WorkerFunc:         introspection.NewWorker,
   237  	}); err != nil {
   238  		// If the introspection worker failed to start, we just log error
   239  		// but continue. It is very unlikely to happen in the real world
   240  		// as the only issue is connecting to the abstract domain socket
   241  		// and the agent is controlled by by the OS to only have one.
   242  		logger.Errorf("failed to start introspection worker: %v", err)
   243  	}
   244  	return engine, nil
   245  }
   246  
   247  // Tag implements Agent.
   248  func (op *CaasOperatorAgent) Tag() names.Tag {
   249  	return names.NewApplicationTag(op.ApplicationName)
   250  }
   251  
   252  // ChangeConfig implements Agent.
   253  func (a *CaasOperatorAgent) ChangeConfig(mutate agent.ConfigMutator) error {
   254  	err := a.AgentConf.ChangeConfig(mutate)
   255  	a.configChangedVal.Set(true)
   256  	return errors.Trace(err)
   257  }
   258  
   259  // validateMigration is called by the migrationminion to help check
   260  // that the agent will be ok when connected to a new controller.
   261  func (op *CaasOperatorAgent) validateMigration(apiCaller base.APICaller) error {
   262  	// TODO(wallyworld) - more extensive checks to come.
   263  	facade := apicaasoperator.NewClient(apiCaller)
   264  	_, err := facade.Life(op.ApplicationName)
   265  	if err != nil {
   266  		return errors.Trace(err)
   267  	}
   268  	model, err := facade.Model()
   269  	if err != nil {
   270  		return errors.Trace(err)
   271  	}
   272  	curModelUUID := op.CurrentConfig().Model().Id()
   273  	newModelUUID := model.UUID
   274  	if newModelUUID != curModelUUID {
   275  		return errors.Errorf("model mismatch when validating: got %q, expected %q",
   276  			newModelUUID, curModelUUID)
   277  	}
   278  	return nil
   279  }