github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/caasoperatorprovisioner/worker.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasoperatorprovisioner 5 6 import ( 7 "strings" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/utils" 12 "gopkg.in/juju/names.v2" 13 "gopkg.in/juju/worker.v1" 14 "gopkg.in/juju/worker.v1/catacomb" 15 16 "github.com/juju/juju/agent" 17 apicaasprovisioner "github.com/juju/juju/api/caasoperatorprovisioner" 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/caas" 20 "github.com/juju/juju/caas/kubernetes/provider" 21 "github.com/juju/juju/core/life" 22 "github.com/juju/juju/core/watcher" 23 "github.com/juju/juju/storage" 24 ) 25 26 var logger = loggo.GetLogger("juju.workers.caasprovisioner") 27 28 // CAASProvisionerFacade exposes CAAS provisioning functionality to a worker. 29 type CAASProvisionerFacade interface { 30 OperatorProvisioningInfo() (apicaasprovisioner.OperatorProvisioningInfo, error) 31 WatchApplications() (watcher.StringsWatcher, error) 32 SetPasswords([]apicaasprovisioner.ApplicationPassword) (params.ErrorResults, error) 33 Life(string) (life.Value, error) 34 } 35 36 // Config defines the operation of a Worker. 37 type Config struct { 38 Facade CAASProvisionerFacade 39 Broker caas.Broker 40 ModelTag names.ModelTag 41 AgentConfig agent.Config 42 } 43 44 // NewProvisionerWorker starts and returns a new CAAS provisioner worker. 45 func NewProvisionerWorker(config Config) (worker.Worker, error) { 46 p := &provisioner{ 47 provisionerFacade: config.Facade, 48 broker: config.Broker, 49 modelTag: config.ModelTag, 50 agentConfig: config.AgentConfig, 51 } 52 err := catacomb.Invoke(catacomb.Plan{ 53 Site: &p.catacomb, 54 Work: p.loop, 55 }) 56 return p, err 57 } 58 59 type provisioner struct { 60 catacomb catacomb.Catacomb 61 provisionerFacade CAASProvisionerFacade 62 broker caas.Broker 63 64 modelTag names.ModelTag 65 agentConfig agent.Config 66 } 67 68 // Kill is part of the worker.Worker interface. 69 func (p *provisioner) Kill() { 70 p.catacomb.Kill(nil) 71 } 72 73 // Wait is part of the worker.Worker interface. 74 func (p *provisioner) Wait() error { 75 return p.catacomb.Wait() 76 } 77 78 func (p *provisioner) loop() error { 79 // TODO(caas) - this loop should also keep an eye on kubernetes and ensure 80 // that the operator stays up, redeploying it if the pod goes 81 // away. For some runtimes we *could* rely on the the runtime's 82 // features to do this. 83 84 appWatcher, err := p.provisionerFacade.WatchApplications() 85 if err != nil { 86 return errors.Trace(err) 87 } 88 if err := p.catacomb.Add(appWatcher); err != nil { 89 return errors.Trace(err) 90 } 91 92 for { 93 select { 94 case <-p.catacomb.Dying(): 95 return p.catacomb.ErrDying() 96 97 // CAAS applications changed so either create or remove pods as appropriate. 98 case apps, ok := <-appWatcher.Changes(): 99 if !ok { 100 return errors.New("app watcher closed channel") 101 } 102 var newApps []string 103 for _, app := range apps { 104 appLife, err := p.provisionerFacade.Life(app) 105 if errors.IsNotFound(err) || appLife == life.Dead { 106 logger.Debugf("deleting operator for %q", app) 107 if err := p.broker.DeleteOperator(app); err != nil { 108 return errors.Annotatef(err, "failed to stop operator for %q", app) 109 } 110 continue 111 } 112 if appLife != life.Alive { 113 continue 114 } 115 newApps = append(newApps, app) 116 } 117 if len(newApps) == 0 { 118 continue 119 } 120 if err := p.ensureOperators(newApps); err != nil { 121 return errors.Trace(err) 122 } 123 } 124 } 125 } 126 127 // ensureOperators creates operator pods for the specified app names -> api passwords. 128 func (p *provisioner) ensureOperators(apps []string) error { 129 var appPasswords []apicaasprovisioner.ApplicationPassword 130 operatorConfig := make([]*caas.OperatorConfig, len(apps)) 131 for i, app := range apps { 132 exists, err := p.broker.OperatorExists(app) 133 if err != nil { 134 return errors.Annotatef(err, "failed to find operator for %q", app) 135 } 136 // If the operator does not exist already, we need to create an initial 137 // password for it. 138 var password string 139 if !exists { 140 if password, err = utils.RandomPassword(); err != nil { 141 return errors.Trace(err) 142 } 143 appPasswords = append(appPasswords, apicaasprovisioner.ApplicationPassword{Name: app, Password: password}) 144 } 145 146 config, err := p.makeOperatorConfig(app, password) 147 if err != nil { 148 return errors.Annotatef(err, "failed to generate operator config for %q", app) 149 } 150 operatorConfig[i] = config 151 } 152 // If we did create any passwords for new operators, first they need 153 // to be saved so the agent can login when it starts up. 154 if len(appPasswords) > 0 { 155 errorResults, err := p.provisionerFacade.SetPasswords(appPasswords) 156 if err != nil { 157 return errors.Annotate(err, "failed to set application api passwords") 158 } 159 if err := errorResults.Combine(); err != nil { 160 return errors.Annotate(err, "failed to set application api passwords") 161 } 162 } 163 164 // Now that any new config/passwords are done, create or update 165 // the operators themselves. 166 var errorStrings []string 167 for i, app := range apps { 168 if err := p.ensureOperator(app, operatorConfig[i]); err != nil { 169 errorStrings = append(errorStrings, err.Error()) 170 continue 171 } 172 } 173 if errorStrings != nil { 174 err := errors.New(strings.Join(errorStrings, "\n")) 175 return errors.Annotate(err, "failed to provision all operators") 176 } 177 return nil 178 } 179 180 func (p *provisioner) ensureOperator(app string, config *caas.OperatorConfig) error { 181 if err := p.broker.EnsureOperator(app, p.agentConfig.DataDir(), config); err != nil { 182 return errors.Annotatef(err, "failed to start operator for %q", app) 183 } 184 logger.Infof("started operator for application %q", app) 185 return nil 186 } 187 188 func (p *provisioner) makeOperatorConfig(appName, password string) (*caas.OperatorConfig, error) { 189 appTag := names.NewApplicationTag(appName) 190 info, err := p.provisionerFacade.OperatorProvisioningInfo() 191 if err != nil { 192 return nil, errors.Trace(err) 193 } 194 // All operators must have storage configured because charms 195 // have persistent state which must be preserved between any 196 // operator restarts. 197 if info.CharmStorage.Provider != provider.K8s_ProviderType { 198 if spType := info.CharmStorage.Provider; spType == "" { 199 return nil, errors.NotValidf("missing operator storage provider") 200 } else { 201 return nil, errors.NotSupportedf("operator storage provider %q", spType) 202 } 203 } 204 logger.Debugf("using caas operator info %+v", info) 205 206 cfg := &caas.OperatorConfig{ 207 OperatorImagePath: info.ImagePath, 208 Version: info.Version, 209 ResourceTags: info.Tags, 210 CharmStorage: charmStorageParams(info.CharmStorage), 211 } 212 // If no password required, we leave the agent conf empty. 213 if password == "" { 214 return cfg, nil 215 } 216 217 conf, err := agent.NewAgentConfig( 218 agent.AgentConfigParams{ 219 Paths: agent.Paths{ 220 DataDir: p.agentConfig.DataDir(), 221 LogDir: p.agentConfig.LogDir(), 222 }, 223 Tag: appTag, 224 Controller: p.agentConfig.Controller(), 225 Model: p.modelTag, 226 APIAddresses: info.APIAddresses, 227 CACert: p.agentConfig.CACert(), 228 Password: password, 229 230 // UpgradedToVersion is mandatory but not used by caas operator agents as they 231 // are not upgraded insitu. 232 UpgradedToVersion: info.Version, 233 }, 234 ) 235 if err != nil { 236 return nil, errors.Trace(err) 237 } 238 239 confBytes, err := conf.Render() 240 if err != nil { 241 return nil, errors.Trace(err) 242 } 243 cfg.AgentConf = confBytes 244 return cfg, nil 245 } 246 247 func charmStorageParams(in storage.KubernetesFilesystemParams) caas.CharmStorageParams { 248 return caas.CharmStorageParams{ 249 Provider: in.Provider, 250 Size: in.Size, 251 Attributes: in.Attributes, 252 ResourceTags: in.ResourceTags, 253 } 254 }