github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/environ/environ.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environ
     5  
     6  import (
     7  	"context"
     8  	"reflect"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/worker/v3/catacomb"
    12  
    13  	"github.com/juju/juju/core/watcher"
    14  	"github.com/juju/juju/environs"
    15  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    16  )
    17  
    18  // logger is here to stop the desire of creating a package level logger.
    19  // Don't do this, instead use the one passed as manifold config.
    20  type logger interface{}
    21  
    22  var _ logger = struct{}{}
    23  
    24  // ConfigObserver exposes a model configuration and a watch constructor
    25  // that allows clients to be informed of changes to the configuration.
    26  type ConfigObserver interface {
    27  	environs.EnvironConfigGetter
    28  	WatchForModelConfigChanges() (watcher.NotifyWatcher, error)
    29  	WatchCloudSpecChanges() (watcher.NotifyWatcher, error)
    30  }
    31  
    32  // Config describes the dependencies of a Tracker.
    33  //
    34  // It's arguable that it should be called TrackerConfig, because of the heavy
    35  // use of model config in this package.
    36  type Config struct {
    37  	Observer       ConfigObserver
    38  	NewEnvironFunc environs.NewEnvironFunc
    39  	Logger         Logger
    40  }
    41  
    42  // Validate returns an error if the config cannot be used to start a Tracker.
    43  func (config Config) Validate() error {
    44  	if config.Observer == nil {
    45  		return errors.NotValidf("nil Observer")
    46  	}
    47  	if config.NewEnvironFunc == nil {
    48  		return errors.NotValidf("nil NewEnvironFunc")
    49  	}
    50  	if config.Logger == nil {
    51  		return errors.NotValidf("nil Logger")
    52  	}
    53  	return nil
    54  }
    55  
    56  // Tracker loads an environment, makes it available to clients, and updates
    57  // the environment in response to config changes until it is killed.
    58  type Tracker struct {
    59  	config           Config
    60  	catacomb         catacomb.Catacomb
    61  	environ          environs.Environ
    62  	currentCloudSpec environscloudspec.CloudSpec
    63  }
    64  
    65  // NewTracker loads an environment from the observer and returns a new Tracker,
    66  // or an error if anything goes wrong. If a tracker is returned, its Environ()
    67  // method is immediately usable.
    68  //
    69  // The caller is responsible for Kill()ing the returned Tracker and Wait()ing
    70  // for any errors it might return.
    71  func NewTracker(config Config) (*Tracker, error) {
    72  	if err := config.Validate(); err != nil {
    73  		return nil, errors.Trace(err)
    74  	}
    75  
    76  	environ, spec, err := environs.GetEnvironAndCloud(config.Observer, config.NewEnvironFunc)
    77  	if err != nil {
    78  		return nil, errors.Trace(err)
    79  	}
    80  
    81  	t := &Tracker{
    82  		config:           config,
    83  		environ:          environ,
    84  		currentCloudSpec: *spec,
    85  	}
    86  	err = catacomb.Invoke(catacomb.Plan{
    87  		Site: &t.catacomb,
    88  		Work: t.loop,
    89  	})
    90  	if err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  	return t, nil
    94  }
    95  
    96  // Environ returns the encapsulated Environ. It will continue to be updated in
    97  // the background for as long as the Tracker continues to run.
    98  func (t *Tracker) Environ() environs.Environ {
    99  	return t.environ
   100  }
   101  
   102  func (t *Tracker) loop() (err error) {
   103  	cfg := t.environ.Config()
   104  	defer errors.DeferredAnnotatef(&err, "model %q (%s)", cfg.Name(), cfg.UUID())
   105  
   106  	logger := t.config.Logger
   107  	environWatcher, err := t.config.Observer.WatchForModelConfigChanges()
   108  	if err != nil {
   109  		return errors.Annotate(err, "watching environ config")
   110  	}
   111  	if err := t.catacomb.Add(environWatcher); err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  
   115  	// Some environs support reacting to changes in the cloud config.
   116  	// Set up a watcher if that's the case.
   117  	var (
   118  		cloudWatcherChanges watcher.NotifyChannel
   119  		cloudSpecSetter     environs.CloudSpecSetter
   120  		ok                  bool
   121  	)
   122  	if cloudSpecSetter, ok = t.environ.(environs.CloudSpecSetter); !ok {
   123  		logger.Warningf("cloud type %v doesn't support dynamic changing of cloud spec", t.environ.Config().Type())
   124  	} else {
   125  		cloudWatcher, err := t.config.Observer.WatchCloudSpecChanges()
   126  		if err != nil {
   127  			return errors.Annotate(err, "cannot watch environ cloud spec")
   128  		}
   129  		if err := t.catacomb.Add(cloudWatcher); err != nil {
   130  			return errors.Trace(err)
   131  		}
   132  		cloudWatcherChanges = cloudWatcher.Changes()
   133  	}
   134  	for {
   135  		logger.Debugf("waiting for environ watch notification")
   136  		select {
   137  		case <-t.catacomb.Dying():
   138  			return t.catacomb.ErrDying()
   139  		case _, ok := <-environWatcher.Changes():
   140  			if !ok {
   141  				return errors.New("environ config watch closed")
   142  			}
   143  			logger.Debugf("reloading environ config")
   144  			modelConfig, err := t.config.Observer.ModelConfig()
   145  			if err != nil {
   146  				return errors.Annotate(err, "reading model config")
   147  			}
   148  			if err = t.environ.SetConfig(modelConfig); err != nil {
   149  				return errors.Annotate(err, "updating environ config")
   150  			}
   151  		case _, ok := <-cloudWatcherChanges:
   152  			if !ok {
   153  				return errors.New("cloud watch closed")
   154  			}
   155  			cloudSpec, err := t.config.Observer.CloudSpec()
   156  			if err != nil {
   157  				return errors.Annotate(err, "reading environ cloud spec")
   158  			}
   159  			if reflect.DeepEqual(cloudSpec, t.currentCloudSpec) {
   160  				continue
   161  			}
   162  			logger.Debugf("reloading cloud config")
   163  			if err = cloudSpecSetter.SetCloudSpec(context.TODO(), cloudSpec); err != nil {
   164  				return errors.Annotate(err, "cannot update environ cloud spec")
   165  			}
   166  			t.currentCloudSpec = cloudSpec
   167  		}
   168  	}
   169  }
   170  
   171  // Kill is part of the worker.Worker interface.
   172  func (t *Tracker) Kill() {
   173  	t.catacomb.Kill(nil)
   174  }
   175  
   176  // Wait is part of the worker.Worker interface.
   177  func (t *Tracker) Wait() error {
   178  	return t.catacomb.Wait()
   179  }