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 }