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  }