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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package presence
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/pubsub"
    11  	"gopkg.in/juju/worker.v1"
    12  	"gopkg.in/tomb.v2"
    13  
    14  	"github.com/juju/juju/core/presence"
    15  	"github.com/juju/juju/pubsub/apiserver"
    16  	"github.com/juju/juju/pubsub/forwarder"
    17  )
    18  
    19  // WorkerConfig defines the configuration values that the pubsub worker needs
    20  // to operate.
    21  type WorkerConfig struct {
    22  	Origin   string
    23  	Hub      *pubsub.StructuredHub
    24  	Recorder presence.Recorder
    25  	Logger   Logger
    26  }
    27  
    28  // Validate ensures that the required values are set in the structure.
    29  func (c *WorkerConfig) Validate() error {
    30  	if c.Origin == "" {
    31  		return errors.NotValidf("missing origin")
    32  	}
    33  	if c.Hub == nil {
    34  		return errors.NotValidf("missing hub")
    35  	}
    36  	if c.Recorder == nil {
    37  		return errors.NotValidf("missing recorder")
    38  	}
    39  	if c.Logger == nil {
    40  		return errors.NotValidf("missing logger")
    41  	}
    42  	return nil
    43  }
    44  
    45  // NewWorker creates a new presence worker that responds to pubsub connection
    46  // messages.
    47  func NewWorker(config WorkerConfig) (worker.Worker, error) {
    48  	if err := config.Validate(); err != nil {
    49  		return nil, errors.Trace(err)
    50  	}
    51  	// Don't return from NewWorker until the loop has started and
    52  	// has subscribed to everything.
    53  	started := make(chan struct{})
    54  	w := &wrapper{
    55  		origin:   config.Origin,
    56  		hub:      config.Hub,
    57  		recorder: config.Recorder,
    58  		logger:   config.Logger,
    59  	}
    60  	w.tomb.Go(func() error {
    61  		return w.loop(started)
    62  	})
    63  	select {
    64  	case <-started:
    65  	case <-time.After(10 * time.Second):
    66  		return nil, errors.New("worker failed to start properly")
    67  	}
    68  	return w, nil
    69  }
    70  
    71  type wrapper struct {
    72  	tomb     tomb.Tomb
    73  	origin   string
    74  	hub      *pubsub.StructuredHub
    75  	recorder presence.Recorder
    76  	logger   Logger
    77  }
    78  
    79  // Report implements worker.Report.
    80  func (w *wrapper) Report() map[string]interface{} {
    81  	all := w.recorder.Connections()
    82  	result := make(map[string]interface{})
    83  	servers := all.Servers()
    84  	for _, name := range servers {
    85  		conns := all.ForServer(name)
    86  		result[name] = conns.Count()
    87  	}
    88  	return result
    89  }
    90  
    91  func (w *wrapper) loop(started chan struct{}) error {
    92  	multiplexer, err := w.hub.NewMultiplexer()
    93  	if err != nil {
    94  		return errors.Trace(err)
    95  	}
    96  	defer multiplexer.Unsubscribe()
    97  
    98  	if err := multiplexer.Add(forwarder.ConnectedTopic, w.forwarderConnect); err != nil {
    99  		return errors.Trace(err)
   100  	}
   101  	if err := multiplexer.Add(forwarder.DisconnectedTopic, w.forwarderDisconnect); err != nil {
   102  		return errors.Trace(err)
   103  	}
   104  	if err := multiplexer.Add(apiserver.ConnectTopic, w.agentLogin); err != nil {
   105  		return errors.Trace(err)
   106  	}
   107  	if err := multiplexer.Add(apiserver.DisconnectTopic, w.agentDisconnect); err != nil {
   108  		return errors.Trace(err)
   109  	}
   110  	if err := multiplexer.Add(apiserver.PresenceRequestTopic, w.presenceRequest); err != nil {
   111  		return errors.Trace(err)
   112  	}
   113  	if err := multiplexer.Add(apiserver.PresenceResponseTopic, w.presenceResponse); err != nil {
   114  		return errors.Trace(err)
   115  	}
   116  	// Let the caller know we are done.
   117  	close(started)
   118  	// Don't exit until we are told to. Exiting unsubscribes.
   119  	<-w.tomb.Dying()
   120  	w.logger.Tracef("presence loop finished")
   121  	return nil
   122  }
   123  
   124  func (w *wrapper) forwarderConnect(topic string, data forwarder.OriginTarget, err error) {
   125  	if err != nil {
   126  		w.logger.Errorf("forwarderConnect error %v", err)
   127  		return
   128  	}
   129  
   130  	// If we have just set up forwarding to another server, or another server
   131  	// has just set up forwarding to us, ask for their presence info.
   132  	w.logger.Tracef("forwarding connection up for %s -> %s", data.Origin, data.Target)
   133  	var request string
   134  	switch {
   135  	case data.Origin == w.origin:
   136  		request = data.Target
   137  	case data.Target == w.origin:
   138  		request = data.Origin
   139  	default:
   140  		return
   141  	}
   142  	w.logger.Tracef("request presence info from %s", request)
   143  	msg := apiserver.OriginTarget{Target: request}
   144  	w.hub.Publish(apiserver.PresenceRequestTopic, msg)
   145  	w.logger.Tracef("request sent")
   146  }
   147  
   148  func (w *wrapper) forwarderDisconnect(topic string, data forwarder.OriginTarget, err error) {
   149  	if err != nil {
   150  		w.logger.Errorf("forwarderChange error %v", err)
   151  		return
   152  	}
   153  	// If we have lost connectivity to the target, we mark the server down.
   154  	// Ideally this would be when the target is no longer forwarding us messages,
   155  	// but we aren't guaranteed to get those messages, so we use the lack of our
   156  	// connectivity to the other machine to indicate that comms are down.
   157  	if data.Origin == w.origin {
   158  		w.logger.Tracef("forwarding connection down for %s", data.Target)
   159  		w.recorder.ServerDown(data.Target)
   160  	}
   161  }
   162  
   163  func (w *wrapper) agentLogin(topic string, data apiserver.APIConnection, err error) {
   164  	if err != nil {
   165  		w.logger.Errorf("agentLogin error %v", err)
   166  		return
   167  	}
   168  	if w.logger.IsTraceEnabled() {
   169  		agentName := data.AgentTag
   170  		if data.ControllerAgent {
   171  			agentName += " (controller)"
   172  		}
   173  		w.logger.Tracef("api connect %s:%s -> %s (%v)", data.ModelUUID, agentName, data.Origin, data.ConnectionID)
   174  	}
   175  	w.recorder.Connect(data.Origin, data.ModelUUID, data.AgentTag, data.ConnectionID, data.ControllerAgent, data.UserData)
   176  }
   177  
   178  func (w *wrapper) agentDisconnect(topic string, data apiserver.APIConnection, err error) {
   179  	if err != nil {
   180  		w.logger.Errorf("agentDisconnect error %v", err)
   181  		return
   182  	}
   183  	w.logger.Tracef("api disconnect %s (%v)", data.Origin, data.ConnectionID)
   184  	w.recorder.Disconnect(data.Origin, data.ConnectionID)
   185  }
   186  
   187  func (w *wrapper) presenceRequest(topic string, data apiserver.OriginTarget, err error) {
   188  	if err != nil {
   189  		w.logger.Errorf("connectionChange error %v", err)
   190  		return
   191  	}
   192  	// If the message is not meant for us, ignore.
   193  	if data.Target != w.origin {
   194  		w.logger.Tracef("presence request for %s ignored, as we are %s", data.Target, w.origin)
   195  		return
   196  	}
   197  
   198  	w.logger.Tracef("presence request from %s", data.Origin)
   199  
   200  	connections := w.recorder.Connections().ForServer(w.origin)
   201  	values := connections.Values()
   202  	response := apiserver.PresenceResponse{
   203  		Connections: make([]apiserver.APIConnection, len(values)),
   204  	}
   205  	for i, value := range values {
   206  		if value.Status != presence.Alive {
   207  			w.logger.Infof("presence response has weird status: %#v", value)
   208  		}
   209  		response.Connections[i] = apiserver.APIConnection{
   210  			AgentTag:        value.Agent,
   211  			ControllerAgent: value.ControllerAgent,
   212  			ModelUUID:       value.Model,
   213  			ConnectionID:    value.ConnectionID,
   214  			Origin:          value.Server,
   215  			UserData:        value.UserData,
   216  		}
   217  	}
   218  	_, err = w.hub.Publish(apiserver.PresenceResponseTopic, response)
   219  	if err != nil {
   220  		w.logger.Errorf("cannot send presence response: %v", err)
   221  	}
   222  }
   223  
   224  func (w *wrapper) presenceResponse(topic string, data apiserver.PresenceResponse, err error) {
   225  	if err != nil {
   226  		w.logger.Errorf("connectionChange error %v", err)
   227  		return
   228  	}
   229  	// If this message is from us, ignore it.
   230  	if data.Origin == w.origin {
   231  		w.logger.Tracef("ignoring our own presence response message")
   232  		return
   233  	}
   234  
   235  	// Build up a slice of presence values so we can transactionally
   236  	// update the recorder.
   237  	values := make([]presence.Value, 0, len(data.Connections))
   238  	for _, conn := range data.Connections {
   239  		if w.logger.IsTraceEnabled() {
   240  			agentName := conn.AgentTag
   241  			if conn.ControllerAgent {
   242  				agentName += " (controller)"
   243  			}
   244  			w.logger.Tracef("setting presence %s:%s -> %s (%v)", conn.ModelUUID, agentName, conn.Origin, conn.ConnectionID)
   245  		}
   246  		values = append(values, presence.Value{
   247  			Model:           conn.ModelUUID,
   248  			Server:          conn.Origin,
   249  			Agent:           conn.AgentTag,
   250  			ConnectionID:    conn.ConnectionID,
   251  			ControllerAgent: conn.ControllerAgent,
   252  			UserData:        conn.UserData,
   253  		})
   254  	}
   255  
   256  	err = w.recorder.UpdateServer(data.Origin, values)
   257  	// An error here is only if the values don't come from the server.
   258  	// This would be a programming error, and as such, we just log it.
   259  	if err != nil {
   260  		w.logger.Errorf("UpdateServer error %v", err)
   261  	}
   262  }
   263  
   264  // Kill implements Worker.Kill().
   265  func (w *wrapper) Kill() {
   266  	w.tomb.Kill(nil)
   267  }
   268  
   269  // Wait implements Worker.Wait().
   270  func (w *wrapper) Wait() error {
   271  	return w.tomb.Wait()
   272  }