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

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