github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/credentialvalidator/worker.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package credentialvalidator
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"gopkg.in/juju/worker.v1"
    10  	"gopkg.in/juju/worker.v1/catacomb"
    11  
    12  	"github.com/juju/juju/api/base"
    13  	"github.com/juju/juju/core/watcher"
    14  )
    15  
    16  var logger = loggo.GetLogger("juju.api.credentialvalidator")
    17  
    18  // ErrValidityChanged indicates that a Worker has bounced because its
    19  // credential validity has changed: either a valid credential became invalid
    20  // or invalid credential became valid.
    21  var ErrValidityChanged = errors.New("cloud credential validity has changed")
    22  
    23  // ErrModelCredentialChanged indicates that a Worker has bounced because its
    24  // model's cloud credential has changed.
    25  var ErrModelCredentialChanged = errors.New("model cloud credential has changed")
    26  
    27  // Facade exposes functionality required by a Worker to access and watch
    28  // a cloud credential that a model uses.
    29  type Facade interface {
    30  	// ModelCredential gets model's cloud credential.
    31  	// Models that are on the clouds that do not require auth will return
    32  	// false to signify that credential was not set.
    33  	ModelCredential() (base.StoredCredential, bool, error)
    34  
    35  	// WatchCredential gets cloud credential watcher.
    36  	WatchCredential(string) (watcher.NotifyWatcher, error)
    37  
    38  	// WatchModelCredential gets model's cloud credential watcher.
    39  	WatchModelCredential() (watcher.NotifyWatcher, error)
    40  }
    41  
    42  // Config holds the dependencies and configuration for a Worker.
    43  type Config struct {
    44  	Facade Facade
    45  }
    46  
    47  // Validate returns an error if the config cannot be expected to
    48  // drive a functional Worker.
    49  func (config Config) Validate() error {
    50  	if config.Facade == nil {
    51  		return errors.NotValidf("nil Facade")
    52  	}
    53  	return nil
    54  }
    55  
    56  // NewWorker returns a Worker that tracks the validity of the Model's cloud
    57  // credential, as exposed by the Facade.
    58  func NewWorker(config Config) (worker.Worker, error) {
    59  	if err := config.Validate(); err != nil {
    60  		return nil, errors.Trace(err)
    61  	}
    62  
    63  	mc, err := modelCredential(config.Facade)
    64  	if err != nil {
    65  		return nil, errors.Trace(err)
    66  	}
    67  
    68  	// This worker needs to monitor both the changes to the credential content that
    69  	// this model uses as well as what credential the model uses.
    70  	// It needs to be restarted if there is a change in either.
    71  	mcw, err := config.Facade.WatchModelCredential()
    72  	if err != nil {
    73  		return nil, errors.Trace(err)
    74  	}
    75  
    76  	v := &validator{
    77  		validatorFacade:        config.Facade,
    78  		credential:             mc,
    79  		modelCredentialWatcher: mcw,
    80  	}
    81  
    82  	// The watcher needs to be added to the worker's catacomb plan
    83  	// here in order to be controlled by this worker's lifecycle events:
    84  	// for example, to be destroyed when this worker is destroyed, etc.
    85  	// We also add the watcher to the Plan.Init collection to ensure that
    86  	// the worker's Plan.Work method is executed after the watcher
    87  	// is initialised and watcher's changes collection obtains the changes.
    88  	// Watchers that are added using catacomb.Add method
    89  	// miss out on a first call of Worker's Plan.Work method and can, thus,
    90  	// be missing out on an initial change.
    91  	plan := catacomb.Plan{
    92  		Site: &v.catacomb,
    93  		Work: v.loop,
    94  		Init: []worker.Worker{v.modelCredentialWatcher},
    95  	}
    96  
    97  	if mc.CloudCredential != "" {
    98  		var err error
    99  		v.credentialWatcher, err = config.Facade.WatchCredential(mc.CloudCredential)
   100  		if err != nil {
   101  			return nil, errors.Trace(err)
   102  		}
   103  		plan.Init = append(plan.Init, v.credentialWatcher)
   104  	}
   105  
   106  	if err := catacomb.Invoke(plan); err != nil {
   107  		return nil, errors.Trace(err)
   108  	}
   109  	return v, nil
   110  }
   111  
   112  type validator struct {
   113  	catacomb        catacomb.Catacomb
   114  	validatorFacade Facade
   115  
   116  	modelCredentialWatcher watcher.NotifyWatcher
   117  
   118  	credential base.StoredCredential
   119  	// could be nil when there is no model credential to watch
   120  	credentialWatcher watcher.NotifyWatcher
   121  }
   122  
   123  // Kill is part of the worker.Worker interface.
   124  func (v *validator) Kill() {
   125  	v.catacomb.Kill(nil)
   126  }
   127  
   128  // Wait is part of the worker.Worker interface.
   129  func (v *validator) Wait() error {
   130  	return v.catacomb.Wait()
   131  }
   132  
   133  // Check is part of the util.Flag interface.
   134  func (v *validator) Check() bool {
   135  	return v.credential.Valid
   136  }
   137  
   138  func (v *validator) loop() error {
   139  	var watcherChanges watcher.NotifyChannel
   140  	if v.credentialWatcher != nil {
   141  		watcherChanges = v.credentialWatcher.Changes()
   142  	}
   143  
   144  	for {
   145  		select {
   146  		case <-v.catacomb.Dying():
   147  			return v.catacomb.ErrDying()
   148  		case _, ok := <-v.modelCredentialWatcher.Changes():
   149  			if !ok {
   150  				return v.catacomb.ErrDying()
   151  			}
   152  			updatedCredential, err := modelCredential(v.validatorFacade)
   153  			if err != nil {
   154  				return errors.Trace(err)
   155  			}
   156  			if v.credential.CloudCredential != updatedCredential.CloudCredential {
   157  				return ErrModelCredentialChanged
   158  			}
   159  		case _, ok := <-watcherChanges:
   160  			if !ok {
   161  				return v.catacomb.ErrDying()
   162  			}
   163  			updatedCredential, err := modelCredential(v.validatorFacade)
   164  			if err != nil {
   165  				return errors.Trace(err)
   166  			}
   167  			if v.credential.Valid != updatedCredential.Valid {
   168  				return ErrValidityChanged
   169  			}
   170  		}
   171  	}
   172  }
   173  
   174  func modelCredential(v Facade) (base.StoredCredential, error) {
   175  	mc, _, err := v.ModelCredential()
   176  	if err != nil {
   177  		return base.StoredCredential{}, errors.Trace(err)
   178  	}
   179  	return mc, nil
   180  }