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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The presence package works on the premise that an agent it alive
     5  // if it has a current connection to one of the API servers.
     6  //
     7  // This package handles all of the logic around collecting an organising
     8  // the information around all the connections made to the API servers.
     9  package presence
    10  
    11  import (
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/juju/collections/set"
    16  	"github.com/juju/errors"
    17  )
    18  
    19  // Recorder records the presence status of every apiserver connection
    20  // for the agents.
    21  type Recorder interface {
    22  	// Disable clears the entries and marks the recorder as disabled. Note
    23  	// that all agents will have a recorder, but they are only enabled for
    24  	// API server agents.
    25  	Disable()
    26  
    27  	// Enable marks the recorder as enabled.
    28  	Enable()
    29  
    30  	// IsEnabled returns whether or not the recorder is enabled.
    31  	IsEnabled() bool
    32  
    33  	// Connect adds an entry for the specified agent.
    34  	// The server and agent strings are the stringified machine and unit tags.
    35  	// The model is the UUID for the model.
    36  	Connect(server, model, agent string, id uint64, controllerAgent bool, userData string)
    37  
    38  	// Disconnect removes the entry for the specified connection id.
    39  	Disconnect(server string, id uint64)
    40  
    41  	// Activity updates the last seen timestamp for the connection specified.
    42  	Activity(server string, id uint64)
    43  
    44  	// ServerDown marks all connections on the specified server as unknown.
    45  	ServerDown(server string)
    46  
    47  	// UpdateServer replaces all known connections for the specified server
    48  	// with the connections specified. If any of the connections are not for
    49  	// the specified server, an error is returned.
    50  	UpdateServer(server string, connections []Value) error
    51  
    52  	// Connections returns all connections info that the recorder has.
    53  	Connections() Connections
    54  }
    55  
    56  // Connections provides a way to slice the full presence understanding
    57  // across various axis like server, model and agent.
    58  type Connections interface {
    59  	// ForModel will return the connections just for the specified model UUID.
    60  	ForModel(model string) Connections
    61  
    62  	// ForServer will return just the connections for agents connected to the specified
    63  	// server. The server is a stringified machine tag for the API server.
    64  	ForServer(server string) Connections
    65  
    66  	// ForAgent returns the connections for the specified agent in the model.
    67  	// The agent is the stringified machine or unit tag.
    68  	ForAgent(agent string) Connections
    69  
    70  	// Count returns the number of connections.
    71  	Count() int
    72  
    73  	// Models returns all the model UUIDs that have connections.
    74  	Models() []string
    75  
    76  	// Servers returns all the API servers that have connections in this
    77  	// collection.
    78  	Servers() []string
    79  
    80  	// Agents returns all the stringified agent tags that have connections
    81  	// in this collection.
    82  	Agents() []string
    83  
    84  	// For a given non controller agent, return the Status for that agent.
    85  	AgentStatus(agent string) (Status, error)
    86  
    87  	// Values returns the connection information for this collection.
    88  	Values() []Value
    89  }
    90  
    91  // Status represents the state of a given agent's presence.
    92  type Status int
    93  
    94  const (
    95  	// Unknown means that the agent specified is not in the collection.
    96  	Unknown Status = iota
    97  
    98  	// Missing means that the agent was connected, but the server that it was
    99  	// connected to has gone away and not yet come back.
   100  	Missing
   101  
   102  	// Alive means that the connection is active.
   103  	Alive
   104  )
   105  
   106  // String implements Stringer.
   107  func (s Status) String() string {
   108  	switch s {
   109  	case Unknown:
   110  		return "unknown"
   111  	case Missing:
   112  		return "missing"
   113  	case Alive:
   114  		return "alive"
   115  	default:
   116  		return ""
   117  	}
   118  }
   119  
   120  // Value holds the information about a single agent connection to an apiserver
   121  // machine.
   122  type Value struct {
   123  	// Model is the model UUID.
   124  	Model string
   125  
   126  	// Server is the stringified machine tag of the API server.
   127  	Server string
   128  
   129  	// Agent is the stringified machine, unit, or application tag of the agent.
   130  	Agent string
   131  
   132  	// ControllerAgent is true if the agent is in the controller model.
   133  	ControllerAgent bool
   134  
   135  	// ConnectionID is the unique identifier given by the API server.
   136  	ConnectionID uint64
   137  
   138  	// Status is either Missing or Alive.
   139  	Status Status
   140  
   141  	// UserData is the user data provided with the Login API call.
   142  	UserData string
   143  
   144  	// LastSeen is the timestamp when the connection was added using
   145  	// Connect, or the last time Activity was called.
   146  	LastSeen time.Time
   147  }
   148  
   149  type connections struct {
   150  	model  string
   151  	values []Value
   152  }
   153  
   154  // Clock provides an interface for dealing with clocks.
   155  type Clock interface {
   156  	// Now returns the current clock time.
   157  	Now() time.Time
   158  }
   159  
   160  // New returns a new empty Recorder.
   161  func New(clock Clock) Recorder {
   162  	return &recorder{clock: clock}
   163  }
   164  
   165  type recorder struct {
   166  	mu      sync.Mutex
   167  	enabled bool
   168  	clock   Clock
   169  	entries []Value
   170  }
   171  
   172  // Disable implements Recorder.
   173  func (r *recorder) Disable() {
   174  	r.mu.Lock()
   175  	defer r.mu.Unlock()
   176  	r.enabled = false
   177  	r.entries = nil
   178  }
   179  
   180  // Enable implements Recorder.
   181  func (r *recorder) Enable() {
   182  	r.mu.Lock()
   183  	defer r.mu.Unlock()
   184  	r.enabled = true
   185  
   186  }
   187  
   188  // IsEnabled implements Recorder.
   189  func (r *recorder) IsEnabled() bool {
   190  	r.mu.Lock()
   191  	defer r.mu.Unlock()
   192  	return r.enabled
   193  }
   194  
   195  func (r *recorder) findIndex(server string, id uint64) int {
   196  	for i, e := range r.entries {
   197  		if e.Server == server && e.ConnectionID == id {
   198  			return i
   199  		}
   200  	}
   201  	return -1
   202  }
   203  
   204  // Connect implements Recorder.
   205  func (r *recorder) Connect(server, model, agent string, id uint64, controllerAgent bool, userData string) {
   206  	r.mu.Lock()
   207  	defer r.mu.Unlock()
   208  	if !r.enabled {
   209  		return
   210  	}
   211  
   212  	pos := r.findIndex(server, id)
   213  	if pos == -1 {
   214  		r.entries = append(r.entries, Value{
   215  			Model:           model,
   216  			Server:          server,
   217  			Agent:           agent,
   218  			ControllerAgent: controllerAgent,
   219  			ConnectionID:    id,
   220  			Status:          Alive,
   221  			UserData:        userData,
   222  			LastSeen:        r.clock.Now(),
   223  		})
   224  	} else {
   225  		// Need to access the value in the array, not a copy of it.
   226  		r.entries[pos].Status = Alive
   227  		r.entries[pos].LastSeen = r.clock.Now()
   228  	}
   229  }
   230  
   231  // Disconnect implements Recorder.
   232  func (r *recorder) Disconnect(server string, id uint64) {
   233  	r.mu.Lock()
   234  	defer r.mu.Unlock()
   235  	if !r.enabled {
   236  		return
   237  	}
   238  
   239  	if pos := r.findIndex(server, id); pos >= 0 {
   240  		if pos == 0 {
   241  			r.entries = r.entries[1:]
   242  		} else {
   243  			r.entries = append(r.entries[0:pos], r.entries[pos+1:]...)
   244  		}
   245  	}
   246  }
   247  
   248  // Activity implements Recorder.
   249  func (r *recorder) Activity(server string, id uint64) {
   250  	r.mu.Lock()
   251  	defer r.mu.Unlock()
   252  	if !r.enabled {
   253  		return
   254  	}
   255  
   256  	if pos := r.findIndex(server, id); pos >= 0 {
   257  		r.entries[pos].LastSeen = r.clock.Now()
   258  	}
   259  }
   260  
   261  // ServerDown implements Recorder.
   262  func (r *recorder) ServerDown(server string) {
   263  	r.mu.Lock()
   264  	defer r.mu.Unlock()
   265  	if !r.enabled {
   266  		return
   267  	}
   268  
   269  	for i, value := range r.entries {
   270  		if value.Server == server {
   271  			r.entries[i].Status = Missing
   272  		}
   273  	}
   274  }
   275  
   276  // UpdateServer implements Recorder.
   277  func (r *recorder) UpdateServer(server string, connections []Value) error {
   278  	r.mu.Lock()
   279  	defer r.mu.Unlock()
   280  	if !r.enabled {
   281  		return errors.New("recorder not enabled")
   282  	}
   283  
   284  	entries := make([]Value, 0, len(r.entries))
   285  	for _, value := range r.entries {
   286  		if value.Server != server {
   287  			entries = append(entries, value)
   288  		}
   289  	}
   290  
   291  	for _, value := range connections {
   292  		if value.Server != server {
   293  			return errors.Errorf("connection server mismatch, got %q expected %q", value.Server, server)
   294  		}
   295  		value.Status = Alive
   296  		value.LastSeen = r.clock.Now()
   297  		entries = append(entries, value)
   298  	}
   299  
   300  	r.entries = entries
   301  	return nil
   302  }
   303  
   304  // Connections implements Recorder.
   305  func (r *recorder) Connections() Connections {
   306  	r.mu.Lock()
   307  	defer r.mu.Unlock()
   308  
   309  	entries := make([]Value, len(r.entries))
   310  	copy(entries, r.entries)
   311  	return &connections{values: entries}
   312  }
   313  
   314  // ForModel implements Connections.
   315  func (c *connections) ForModel(model string) Connections {
   316  	var values []Value
   317  	for _, value := range c.values {
   318  		if value.Model == model {
   319  			values = append(values, value)
   320  		}
   321  	}
   322  	return &connections{model: model, values: values}
   323  }
   324  
   325  // ForServer implements Connections.
   326  func (c *connections) ForServer(server string) Connections {
   327  	var values []Value
   328  	for _, value := range c.values {
   329  		if value.Server == server {
   330  			values = append(values, value)
   331  		}
   332  	}
   333  	return &connections{model: c.model, values: values}
   334  }
   335  
   336  // ForAgent implements Connections.
   337  func (c *connections) ForAgent(agent string) Connections {
   338  	var values []Value
   339  	for _, value := range c.values {
   340  		if value.Agent == agent {
   341  			values = append(values, value)
   342  		}
   343  	}
   344  	return &connections{model: c.model, values: values}
   345  }
   346  
   347  // Count implements Connections.
   348  func (c *connections) Count() int {
   349  	return len(c.values)
   350  }
   351  
   352  // Models implements Connections.
   353  func (c *connections) Models() []string {
   354  	models := set.NewStrings()
   355  	for _, value := range c.values {
   356  		models.Add(value.Model)
   357  	}
   358  	return models.Values()
   359  }
   360  
   361  // Servers implements Connections.
   362  func (c *connections) Servers() []string {
   363  	servers := set.NewStrings()
   364  	for _, value := range c.values {
   365  		servers.Add(value.Server)
   366  	}
   367  	return servers.Values()
   368  }
   369  
   370  // Agents implements Connections.
   371  func (c *connections) Agents() []string {
   372  	agents := set.NewStrings()
   373  	for _, value := range c.values {
   374  		agents.Add(value.Agent)
   375  	}
   376  	return agents.Values()
   377  }
   378  
   379  // AgentStatus implements Connections.
   380  func (c *connections) AgentStatus(agent string) (Status, error) {
   381  	if c.model == "" {
   382  		return Unknown, errors.New("connections not limited to a model, agent ambiguous")
   383  	}
   384  	result := Unknown
   385  	for _, value := range c.values {
   386  		if value.Agent == agent && !value.ControllerAgent {
   387  			if value.Status > result {
   388  				result = value.Status
   389  			}
   390  		}
   391  	}
   392  	return result, nil
   393  }
   394  
   395  // Values implements Connections.
   396  func (c *connections) Values() []Value {
   397  	return c.values
   398  }