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