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 }