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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package externalcontrollerupdater
     5  
     6  import (
     7  	"io"
     8  	"reflect"
     9  	"time"
    10  
    11  	"github.com/juju/clock"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"gopkg.in/juju/names.v2"
    15  	"gopkg.in/juju/worker.v1"
    16  	"gopkg.in/juju/worker.v1/catacomb"
    17  
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/crosscontroller"
    20  	"github.com/juju/juju/core/crossmodel"
    21  	"github.com/juju/juju/core/watcher"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.worker.externalcontrollerupdater")
    25  
    26  // ExternalControllerWatcherClient defines the interface for watching changes
    27  // to the local controller's external controller records, and obtaining and
    28  // updating their values. This will communicate only with the local controller.
    29  type ExternalControllerUpdaterClient interface {
    30  	WatchExternalControllers() (watcher.StringsWatcher, error)
    31  	ExternalControllerInfo(controllerUUID string) (*crossmodel.ControllerInfo, error)
    32  	SetExternalControllerInfo(crossmodel.ControllerInfo) error
    33  }
    34  
    35  // ExternalControllerWatcherClientCloser extends the ExternalControllerWatcherClient
    36  // interface with a Close method, for closing the API connection associated with
    37  // the client.
    38  type ExternalControllerWatcherClientCloser interface {
    39  	ExternalControllerWatcherClient
    40  	io.Closer
    41  }
    42  
    43  // ExternalControllerWatcherClient defines the interface for watching changes
    44  // to and obtaining the current API information for a controller. This will
    45  // communicate with an external controller.
    46  type ExternalControllerWatcherClient interface {
    47  	WatchControllerInfo() (watcher.NotifyWatcher, error)
    48  	ControllerInfo() (*crosscontroller.ControllerInfo, error)
    49  }
    50  
    51  // NewExternalControllerWatcherClientFunc is a function type that
    52  // returns an ExternalControllerWatcherClientCloser, given an
    53  // *api.Info. The api.Info should be for making a controller-only
    54  // connection to a remote/external controller.
    55  type NewExternalControllerWatcherClientFunc func(*api.Info) (ExternalControllerWatcherClientCloser, error)
    56  
    57  // New returns a new external controller updater worker.
    58  func New(
    59  	externalControllers ExternalControllerUpdaterClient,
    60  	newExternalControllerWatcherClient NewExternalControllerWatcherClientFunc,
    61  	clock clock.Clock,
    62  ) (worker.Worker, error) {
    63  	w := updaterWorker{
    64  		watchExternalControllers:           externalControllers.WatchExternalControllers,
    65  		externalControllerInfo:             externalControllers.ExternalControllerInfo,
    66  		setExternalControllerInfo:          externalControllers.SetExternalControllerInfo,
    67  		newExternalControllerWatcherClient: newExternalControllerWatcherClient,
    68  		runner: worker.NewRunner(worker.RunnerParams{
    69  			// One of the controller watchers fails should not
    70  			// prevent the others from running.
    71  			IsFatal: func(error) bool { return false },
    72  
    73  			// If the API connection fails, try again in 1 minute.
    74  			RestartDelay: time.Minute,
    75  			Clock:        clock,
    76  		}),
    77  	}
    78  	if err := catacomb.Invoke(catacomb.Plan{
    79  		Site: &w.catacomb,
    80  		Work: w.loop,
    81  		Init: []worker.Worker{w.runner},
    82  	}); err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  	return &w, nil
    86  }
    87  
    88  type updaterWorker struct {
    89  	catacomb catacomb.Catacomb
    90  	runner   *worker.Runner
    91  
    92  	watchExternalControllers           func() (watcher.StringsWatcher, error)
    93  	externalControllerInfo             func(controllerUUID string) (*crossmodel.ControllerInfo, error)
    94  	setExternalControllerInfo          func(crossmodel.ControllerInfo) error
    95  	newExternalControllerWatcherClient NewExternalControllerWatcherClientFunc
    96  }
    97  
    98  // Kill is part of the worker.Worker interface.
    99  func (w *updaterWorker) Kill() {
   100  	w.catacomb.Kill(nil)
   101  }
   102  
   103  // Wait is part of the worker.Worker interface.
   104  func (w *updaterWorker) Wait() error {
   105  	return w.catacomb.Wait()
   106  }
   107  
   108  func (w *updaterWorker) loop() error {
   109  	watcher, err := w.watchExternalControllers()
   110  	if err != nil {
   111  		return errors.Annotate(err, "watching external controllers")
   112  	}
   113  	w.catacomb.Add(watcher)
   114  
   115  	for {
   116  		select {
   117  		case <-w.catacomb.Dying():
   118  			return w.catacomb.ErrDying()
   119  
   120  		case ids, ok := <-watcher.Changes():
   121  			if !ok {
   122  				return w.catacomb.ErrDying()
   123  			}
   124  
   125  			if len(ids) == 0 {
   126  				continue
   127  			}
   128  
   129  			logger.Debugf("external controllers changed: %q", ids)
   130  			tags := make([]names.ControllerTag, len(ids))
   131  			for i, id := range ids {
   132  				if !names.IsValidController(id) {
   133  					return errors.Errorf("%q is not a valid controller tag", id)
   134  				}
   135  				tags[i] = names.NewControllerTag(id)
   136  			}
   137  
   138  			watchers := names.NewSet()
   139  			for _, tag := range tags {
   140  				// We're informed when an external controller
   141  				// is added or removed, so treat as a toggle.
   142  				if watchers.Contains(tag) {
   143  					logger.Infof("stopping watcher for external controller %q", tag.Id())
   144  					w.runner.StopWorker(tag.Id())
   145  					watchers.Remove(tag)
   146  					continue
   147  				}
   148  				logger.Infof("starting watcher for external controller %q", tag.Id())
   149  				watchers.Add(tag)
   150  				if err := w.runner.StartWorker(tag.Id(), func() (worker.Worker, error) {
   151  					cw := controllerWatcher{
   152  						tag:                                tag,
   153  						setExternalControllerInfo:          w.setExternalControllerInfo,
   154  						externalControllerInfo:             w.externalControllerInfo,
   155  						newExternalControllerWatcherClient: w.newExternalControllerWatcherClient,
   156  					}
   157  					if err := catacomb.Invoke(catacomb.Plan{
   158  						Site: &cw.catacomb,
   159  						Work: cw.loop,
   160  					}); err != nil {
   161  						return nil, errors.Trace(err)
   162  					}
   163  					return &cw, nil
   164  				}); err != nil {
   165  					return errors.Annotatef(err, "starting watcher for external controller %q", tag.Id())
   166  				}
   167  			}
   168  		}
   169  	}
   170  }
   171  
   172  // controllerWatcher is a worker that watches for changes to the external
   173  // controller with the given tag. The external controller must be known
   174  // to the local controller.
   175  type controllerWatcher struct {
   176  	catacomb catacomb.Catacomb
   177  
   178  	tag                                names.ControllerTag
   179  	setExternalControllerInfo          func(crossmodel.ControllerInfo) error
   180  	externalControllerInfo             func(controllerUUID string) (*crossmodel.ControllerInfo, error)
   181  	newExternalControllerWatcherClient NewExternalControllerWatcherClientFunc
   182  }
   183  
   184  // Kill is part of the worker.Worker interface.
   185  func (w *controllerWatcher) Kill() {
   186  	w.catacomb.Kill(nil)
   187  }
   188  
   189  // Wait is part of the worker.Worker interface.
   190  func (w *controllerWatcher) Wait() error {
   191  	return w.catacomb.Wait()
   192  }
   193  
   194  func (w *controllerWatcher) loop() error {
   195  	// We get the API info from the local controller initially.
   196  	info, err := w.externalControllerInfo(w.tag.Id())
   197  	if err != nil {
   198  		return errors.Annotate(err, "getting cached external controller info")
   199  	}
   200  	logger.Debugf("controller info for controller %q: %v", w.tag.Id(), info)
   201  
   202  	var nw watcher.NotifyWatcher
   203  	var client ExternalControllerWatcherClientCloser
   204  	defer func() {
   205  		if client != nil {
   206  			client.Close()
   207  		}
   208  	}()
   209  
   210  	for {
   211  		if client == nil {
   212  			apiInfo := &api.Info{
   213  				Addrs:  info.Addrs,
   214  				CACert: info.CACert,
   215  				Tag:    names.NewUserTag(api.AnonymousUsername),
   216  			}
   217  			client, err = w.newExternalControllerWatcherClient(apiInfo)
   218  			if err != nil {
   219  				return errors.Annotate(err, "getting external controller client")
   220  			}
   221  			nw, err = client.WatchControllerInfo()
   222  			if err != nil {
   223  				return errors.Annotate(err, "watching external controller")
   224  			}
   225  			w.catacomb.Add(nw)
   226  		}
   227  
   228  		select {
   229  		case <-w.catacomb.Dying():
   230  			return w.catacomb.ErrDying()
   231  		case _, ok := <-nw.Changes():
   232  			if !ok {
   233  				return w.catacomb.ErrDying()
   234  			}
   235  
   236  			newInfo, err := client.ControllerInfo()
   237  			if err != nil {
   238  				return errors.Annotate(err, "getting external controller info")
   239  			}
   240  			if reflect.DeepEqual(newInfo.Addrs, info.Addrs) {
   241  				continue
   242  			}
   243  			// API addresses have changed. Save the details to the
   244  			// local controller and stop the existing notify watcher
   245  			// and set it to nil, so we'll restart it with the new
   246  			// addresses.
   247  			info.Addrs = newInfo.Addrs
   248  			if err := w.setExternalControllerInfo(crossmodel.ControllerInfo{
   249  				ControllerTag: w.tag,
   250  				Alias:         info.Alias,
   251  				Addrs:         info.Addrs,
   252  				CACert:        info.CACert,
   253  			}); err != nil {
   254  				return errors.Annotate(err, "caching external controller info")
   255  			}
   256  			logger.Infof("new controller info for controller %q: %v", w.tag.Id(), info)
   257  			if err := worker.Stop(nw); err != nil {
   258  				return errors.Trace(err)
   259  			}
   260  			if err := client.Close(); err != nil {
   261  				return errors.Trace(err)
   262  			}
   263  			client = nil
   264  			nw = nil
   265  		}
   266  	}
   267  }