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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package pubsub
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/juju/clock"
    13  	"github.com/juju/collections/set"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/pubsub"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/juju/worker.v1"
    18  	"gopkg.in/juju/worker.v1/catacomb"
    19  
    20  	"github.com/juju/juju/api"
    21  	"github.com/juju/juju/apiserver/params"
    22  	"github.com/juju/juju/pubsub/apiserver"
    23  )
    24  
    25  // WorkerConfig defines the configuration values that the pubsub worker needs
    26  // to operate.
    27  type WorkerConfig struct {
    28  	Origin string
    29  	Clock  clock.Clock
    30  	Hub    *pubsub.StructuredHub
    31  	Logger Logger
    32  
    33  	APIInfo   *api.Info
    34  	NewWriter func(*api.Info) (MessageWriter, error)
    35  	NewRemote func(RemoteServerConfig) (RemoteServer, error)
    36  }
    37  
    38  // Validate checks that all the values have been set.
    39  func (c *WorkerConfig) Validate() error {
    40  	if c.Origin == "" {
    41  		return errors.NotValidf("missing origin")
    42  	}
    43  	if c.Clock == nil {
    44  		return errors.NotValidf("missing clock")
    45  	}
    46  	if c.Hub == nil {
    47  		return errors.NotValidf("missing hub")
    48  	}
    49  	if c.Logger == nil {
    50  		return errors.NotValidf("missing logger")
    51  	}
    52  	if c.APIInfo == nil {
    53  		return errors.NotValidf("missing api info")
    54  	}
    55  	if c.NewWriter == nil {
    56  		return errors.NotValidf("missing new writer")
    57  	}
    58  	if c.NewRemote == nil {
    59  		return errors.NotValidf("missing new remote")
    60  	}
    61  	return nil
    62  }
    63  
    64  // NewWorker exposes the subscriber as a Worker.
    65  func NewWorker(config WorkerConfig) (worker.Worker, error) {
    66  	return newSubscriber(config)
    67  }
    68  
    69  type subscriber struct {
    70  	config   WorkerConfig
    71  	catacomb catacomb.Catacomb
    72  
    73  	unsubAll           func()
    74  	unsubServerDetails func()
    75  
    76  	// servers represent connections to each of the other api servers.
    77  	servers map[string]RemoteServer
    78  	mutex   sync.Mutex
    79  }
    80  
    81  func newSubscriber(config WorkerConfig) (*subscriber, error) {
    82  	if err := config.Validate(); err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  	sub := &subscriber{
    86  		config:  config,
    87  		servers: make(map[string]RemoteServer),
    88  	}
    89  	unsub, err := config.Hub.SubscribeMatch(pubsub.MatchAll, sub.forwardMessage)
    90  	if err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  	sub.unsubAll = unsub
    94  	config.Logger.Debugf("subscribing to details topic")
    95  	unsub, err = config.Hub.Subscribe(apiserver.DetailsTopic, sub.apiServerChanges)
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	sub.unsubServerDetails = unsub
   100  
   101  	// Ask for the current server details now that we're subscribed.
   102  	detailsRequest := apiserver.DetailsRequest{
   103  		Requester: "pubsub-forwarder",
   104  		LocalOnly: true,
   105  	}
   106  	if _, err := config.Hub.Publish(apiserver.DetailsRequestTopic, detailsRequest); err != nil {
   107  		return nil, errors.Trace(err)
   108  	}
   109  
   110  	err = catacomb.Invoke(catacomb.Plan{
   111  		Site: &sub.catacomb,
   112  		Work: sub.waitForDeath,
   113  	})
   114  	if err != nil {
   115  		return nil, errors.Trace(err)
   116  	}
   117  
   118  	return sub, nil
   119  }
   120  
   121  // Report returns the same information as the introspection report
   122  // but in the map for for the dependency engine report.
   123  func (s *subscriber) Report() map[string]interface{} {
   124  	s.mutex.Lock()
   125  	defer s.mutex.Unlock()
   126  	result := map[string]interface{}{
   127  		"source": s.config.Origin,
   128  	}
   129  	targets := make(map[string]interface{})
   130  	for target, remote := range s.servers {
   131  		targets[target] = remote.Report()
   132  	}
   133  	if len(targets) > 0 {
   134  		result["targets"] = targets
   135  	}
   136  	return result
   137  
   138  }
   139  
   140  // IntrospectionReport is the method called by the introspection
   141  // worker to get what to show to the user.
   142  func (s *subscriber) IntrospectionReport() string {
   143  	s.mutex.Lock()
   144  	defer s.mutex.Unlock()
   145  
   146  	var result []string
   147  	for target, remote := range s.servers {
   148  		info := fmt.Sprintf("Target: %s\n%s",
   149  			target, remote.IntrospectionReport())
   150  		result = append(result, info)
   151  	}
   152  	prefix := fmt.Sprintf("Source: %s\n\n", s.config.Origin)
   153  	// Sorting the result gives us consisten ordering.
   154  	sort.Strings(result)
   155  	return prefix + strings.Join(result, "\n")
   156  }
   157  
   158  func (s *subscriber) waitForDeath() error {
   159  	s.config.Logger.Tracef("wait for catacomb dying before unsubscribe")
   160  	defer s.unsubAll()
   161  	defer s.unsubServerDetails()
   162  
   163  	<-s.catacomb.Dying()
   164  	s.config.Logger.Tracef("dying now")
   165  	return s.catacomb.ErrDying()
   166  }
   167  
   168  func (s *subscriber) apiServerChanges(topic string, details apiserver.Details, err error) {
   169  	s.config.Logger.Tracef("apiServerChanges: %#v", details)
   170  	// Make sure we have workers for the defined details.
   171  	if err != nil {
   172  		// This should never happen.
   173  		s.config.Logger.Errorf("subscriber callback error: %v", err)
   174  		return
   175  	}
   176  
   177  	s.mutex.Lock()
   178  	defer s.mutex.Unlock()
   179  	apiServers := set.NewStrings()
   180  	for id, apiServer := range details.Servers {
   181  		target := names.NewMachineTag(id).String()
   182  		apiServers.Add(target)
   183  		if target == s.config.Origin {
   184  			// We don't need to forward messages to ourselves.
   185  			continue
   186  		}
   187  
   188  		// TODO: always use the internal address?
   189  		addresses := apiServer.Addresses
   190  		if apiServer.InternalAddress != "" {
   191  			addresses = []string{apiServer.InternalAddress}
   192  		}
   193  
   194  		server, found := s.servers[target]
   195  		if found {
   196  			s.config.Logger.Tracef("update addresses for %s to %v", target, addresses)
   197  			server.UpdateAddresses(addresses)
   198  		} else {
   199  			s.config.Logger.Debugf("new forwarder for %s", target)
   200  			newInfo := *s.config.APIInfo
   201  			newInfo.Addrs = addresses
   202  			server, err := s.config.NewRemote(RemoteServerConfig{
   203  				Hub:       s.config.Hub,
   204  				Origin:    s.config.Origin,
   205  				Target:    target,
   206  				Clock:     s.config.Clock,
   207  				Logger:    s.config.Logger,
   208  				APIInfo:   &newInfo,
   209  				NewWriter: s.config.NewWriter,
   210  			})
   211  			if err != nil {
   212  				s.config.Logger.Errorf("unable to add new remote server for %q, %v", target, err)
   213  				continue
   214  			}
   215  			s.servers[target] = server
   216  			s.catacomb.Add(server)
   217  		}
   218  	}
   219  	for name, server := range s.servers {
   220  		if !apiServers.Contains(name) {
   221  			s.config.Logger.Debugf("%s no longer listed as an apiserver", name)
   222  			server.Kill()
   223  			err := server.Wait()
   224  			if err != nil {
   225  				s.config.Logger.Errorf("%v", err)
   226  			}
   227  			delete(s.servers, name)
   228  		}
   229  	}
   230  	s.config.Logger.Tracef("update complete")
   231  }
   232  
   233  func (s *subscriber) forwardMessage(topic string, data map[string]interface{}) {
   234  	if data["origin"] != s.config.Origin {
   235  		// Message does not originate from the place we care about.
   236  		// Nothing to do.
   237  		s.config.Logger.Tracef("skipping message %q as origin not ours", topic)
   238  		return
   239  	}
   240  	// If local-only isn't specified, then the default interface{} value is
   241  	// returned, which is nil, and nil isn't true.
   242  	if data["local-only"] == true {
   243  		// Local message, don't forward.
   244  		s.config.Logger.Tracef("skipping message %q as local-only", topic)
   245  		return
   246  	}
   247  
   248  	s.config.Logger.Tracef("forward message %q", topic)
   249  	message := &params.PubSubMessage{Topic: topic, Data: data}
   250  	s.mutex.Lock()
   251  	defer s.mutex.Unlock()
   252  	for _, remote := range s.servers {
   253  		remote.Publish(message)
   254  	}
   255  }
   256  
   257  // Kill is part of the worker.Worker interface.
   258  func (s *subscriber) Kill() {
   259  	s.catacomb.Kill(nil)
   260  }
   261  
   262  // Wait is part of the worker.Worker interface.
   263  func (s *subscriber) Wait() error {
   264  	return s.catacomb.Wait()
   265  }