github.com/grafana/synthetic-monitoring-agent@v0.64.2/cmd/test-api/internal/grpc/checks.go (about) 1 package grpc 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/gogo/status" 11 sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring" 12 "github.com/rs/zerolog" 13 "google.golang.org/grpc/codes" 14 ) 15 16 type ChecksDb interface { 17 FindProbeByID(ctx context.Context, id int64) (*sm.Probe, error) 18 ListChecksForProbe(ctx context.Context, id int64) ([]sm.Check, error) 19 } 20 21 type ChecksServer struct { 22 logger zerolog.Logger 23 db ChecksDb 24 probesMutex sync.Mutex 25 probes map[int64]probeController 26 } 27 28 type ChecksServerOpts struct { 29 Logger zerolog.Logger 30 Db ChecksDb 31 } 32 33 func NewChecksServer(opts ChecksServerOpts) (*ChecksServer, error) { 34 return &ChecksServer{ 35 logger: opts.Logger, 36 db: opts.Db, 37 probes: make(map[int64]probeController), 38 }, nil 39 } 40 41 func (s *ChecksServer) Run(ctx context.Context) error { 42 // This is a dummy placeholder in case updating checks / probes / 43 // tenants is needed at some point. The idea is that the ChecksServer 44 // would monitor a channel with the updates and it would communicate 45 // them to the appropriate probes. 46 // 47 // The code below already sort of keeps track of what's running where. 48 <-ctx.Done() 49 return nil 50 } 51 52 func (s *ChecksServer) RegisterProbe(ctx context.Context, _ *sm.ProbeInfo) (*sm.RegisterProbeResult, error) { 53 probeID, found := probeIdFromContext(ctx) 54 if !found { 55 // This should never happen, the unary handler should have 56 // added the probe ID to the context. 57 return &sm.RegisterProbeResult{ 58 Status: sm.Status{ 59 Code: sm.StatusCode_NOT_AUTHORIZED, 60 Message: "cannot get probe ID", 61 }, 62 }, nil 63 } 64 65 s.acquireProbesMutex(probeID) 66 defer s.releaseProbesMutex(probeID) 67 68 if _, found := s.probes[probeID]; found { 69 // there's an existing probe with this ID 70 return &sm.RegisterProbeResult{ 71 Status: sm.Status{ 72 Code: sm.StatusCode_ALREADY_EXISTS, 73 Message: "probe already exists", 74 }, 75 }, nil 76 } 77 78 ready := make(chan readySignal) 79 80 s.probes[probeID] = probeController{ 81 ch: make(chan sm.Changes, probeUpdatesQueueLenght), 82 adHocCheckCh: make(chan sm.AdHocCheck, probeUpdatesQueueLenght), 83 restart: make(chan restartSignal, probeUpdatesQueueLenght), 84 ready: ready, 85 probeGoneCh: make(chan probeGoneSignal), 86 cleanupCh: make(chan cleanupSignal), 87 } 88 89 go s.waitForProbe(probeID, probeRegistrationTimeout, ready) 90 91 probe, err := s.db.FindProbeByID(ctx, probeID) 92 if err != nil { 93 return &sm.RegisterProbeResult{ 94 Status: sm.Status{ 95 Code: sm.StatusCode_INTERNAL_ERROR, 96 Message: "internal error retrieving probe", 97 }, 98 }, nil 99 } 100 101 return &sm.RegisterProbeResult{Probe: *probe}, nil 102 } 103 104 func (s *ChecksServer) GetChanges(currentState *sm.ProbeState, stream sm.Checks_GetChangesServer) error { 105 probeID, found := probeIdFromContext(stream.Context()) 106 if !found { 107 return errors.New("invalid probe authorization") 108 } 109 110 defer s.deactivateProbe(probeID) 111 112 updates, restart, probeGoneCh, err := s.activateProbe(probeID) 113 if err != nil { 114 return fmt.Errorf("activating probe %d: %w", probeID, err) 115 } 116 117 // Load all the existing checks from the database and send them to client 118 119 existingChecks, err := s.sendInitialChanges(currentState, stream, probeID) 120 if err != nil { 121 return err 122 } 123 124 for { 125 select { 126 case up, ok := <-updates: 127 // If the updates channel was closed, ok will be false. We should never hit 128 // this, because place where we close the channel is in the deactivateProbe 129 // call that happens when exiting this function. 130 if !ok { 131 continue 132 } 133 134 _, err := s.processUpdate(up, probeID, existingChecks, stream) 135 if err != nil { 136 return err 137 } 138 139 case <-restart: 140 return status.Error(codes.Aborted, "operation aborted") 141 142 case <-stream.Context().Done(): 143 return nil 144 145 case <-probeGoneCh: 146 // Probe is gone, stop. 147 return status.Error(codes.Aborted, "operation aborted") 148 } 149 } 150 } 151 152 func (s *ChecksServer) Ping(ctx context.Context, req *sm.PingRequest) (*sm.PongResponse, error) { 153 if _, found := probeIdFromContext(ctx); !found { 154 return nil, errors.New("probe not found") 155 } 156 157 return &sm.PongResponse{Sequence: req.Sequence}, nil 158 } 159 160 const ( 161 probeUpdatesQueueLenght = 128 162 probeRegistrationTimeout = 1 * time.Second 163 ) 164 165 type readySignal struct{} 166 167 type restartSignal struct{} 168 169 type probeGoneSignal struct{} 170 171 type cleanupSignal struct{} 172 173 type probeController struct { 174 ch chan sm.Changes 175 adHocCheckCh chan sm.AdHocCheck 176 restart chan restartSignal 177 ready chan readySignal 178 probeGoneCh chan probeGoneSignal 179 cleanupCh chan cleanupSignal 180 } 181 182 type checksSet map[int64]struct{} 183 184 func (s *ChecksServer) acquireProbesMutex(id int64) { 185 s.probesMutex.Lock() 186 } 187 188 func (s *ChecksServer) releaseProbesMutex(id int64) { 189 s.probesMutex.Unlock() 190 } 191 192 func (s *ChecksServer) waitForProbe(id int64, timeout time.Duration, ready <-chan readySignal) { 193 // Wait for the specified timeout for the probe to 194 // ask for changes (handled in GetChanges); if the probe does 195 // not show up, consider it dead and deactivate it. 196 // 197 // This handles a case where the probe managed to register 198 // itself but didn't proceed thru to get changes. 199 timer := time.NewTimer(timeout) 200 defer timer.Stop() 201 202 select { 203 case <-timer.C: 204 s.logger.Warn().Int64("probe_id", id).Msg("timed out waiting for probe") 205 s.deactivateProbe(id) 206 207 case <-ready: 208 s.logger.Info().Int64("probe_id", id).Msg("probe ready") 209 } 210 } 211 212 // activateProbe searches for an existing probe and marks it as ready. 213 func (s *ChecksServer) activateProbe(id int64) (chan sm.Changes, chan restartSignal, chan probeGoneSignal, error) { 214 s.acquireProbesMutex(id) 215 defer s.releaseProbesMutex(id) 216 217 updates, found := s.probes[id] 218 if !found { 219 return nil, nil, nil, fmt.Errorf("unknown probe") 220 } 221 222 close(updates.ready) 223 224 return updates.ch, updates.restart, updates.probeGoneCh, nil 225 } 226 227 // deactivateProbe searches for an active probe and _removes_ it from the active list. 228 func (s *ChecksServer) deactivateProbe(id int64) { 229 s.acquireProbesMutex(id) 230 231 updates, found := s.probes[id] 232 if found { 233 delete(s.probes, id) 234 } 235 236 s.releaseProbesMutex(id) 237 238 if !found { 239 return 240 } 241 242 DRAIN: 243 for { 244 select { 245 case <-updates.ch: 246 247 case <-updates.restart: 248 249 default: 250 break DRAIN 251 } 252 } 253 254 close(updates.ch) 255 close(updates.restart) 256 close(updates.cleanupCh) 257 } 258 259 func (s *ChecksServer) processUpdate(update sm.Changes, probeID int64, existingChecks checksSet, stream sm.Checks_GetChangesServer) (bool, error) { 260 checks := make([]sm.CheckChange, 0, len(update.Checks)) 261 262 for _, checkChange := range update.Checks { 263 newChange := filterUpdate(probeID, checkChange, existingChecks) 264 if newChange == nil { 265 continue 266 } 267 268 checks = append(checks, *newChange) 269 270 switch newChange.Operation { 271 case sm.CheckOperation_CHECK_ADD: 272 existingChecks[newChange.Check.Id] = struct{}{} 273 274 case sm.CheckOperation_CHECK_UPDATE: 275 // updates got converted to adds (handled above), 276 // deletes (handle below), or already exist in this map 277 // (don't do anything). 278 279 case sm.CheckOperation_CHECK_DELETE: 280 delete(existingChecks, newChange.Check.Id) 281 } 282 } 283 284 update.Checks = checks 285 286 if len(update.Checks) == 0 && len(update.Tenants) == 0 { 287 return false, nil 288 } 289 290 // we have something that should be processed by this probe 291 if err := stream.Send(&update); err != nil { 292 return false, err 293 } 294 295 return true, nil 296 } 297 298 func filterUpdate(probeID int64, up sm.CheckChange, existingChecks checksSet) *sm.CheckChange { 299 // There's an interesting problem here. 300 // 301 // For adds we can simply look at the check to see if _this_ probe is 302 // in the list of probes where the check should run and send the 303 // change to that probe. 304 // 305 // For deletes, the change operation only contains the check's ID 306 // without other details, so we need to keep track of which checks are 307 // running on this probe to now if the delete operation should be sent 308 // there or not. 309 // 310 // For updates, if the probe is being added to the list, sending the 311 // change operation would cause the check to start running on that 312 // probe, but for removals, if we simply look at the list of probes in 313 // the check itself, we would not send the change to a probe where the 314 // check is already running. 315 // 316 // id | listed | existing | enabled | operation | result 317 // ---+--------+----------+---------+-----------+-------- 318 // 1 | false | false | false | ADD | SKIP 319 // 2 | false | false | false | DELETE | SKIP 320 // 3 | false | false | false | UPDATE | SKIP 321 // 4 | false | false | true | ADD | SKIP 322 // 5 | false | false | true | DELETE | SKIP 323 // 6 | false | false | true | UPDATE | SKIP 324 // 7 | false | true | false | ADD | DELETE 325 // 8 | false | true | true | ADD | DELETE 326 // 9 | false | true | false | DELETE | DELETE 327 // 10 | false | true | false | UPDATE | DELETE 328 // 11 | false | true | true | DELETE | DELETE 329 // 12 | false | true | true | UPDATE | DELETE 330 // 13 | true | false | false | ADD | SKIP 331 // 14 | true | false | false | DELETE | SKIP 332 // 15 | true | false | false | UPDATE | SKIP 333 // 16 | true | false | true | ADD | ADD 334 // 17 | true | false | true | DELETE | SKIP 335 // 18 | true | false | true | UPDATE | ADD 336 // 19 | true | true | false | ADD | SKIP -> DELETE 337 // 20 | true | true | false | DELETE | DELETE 338 // 21 | true | true | false | UPDATE | DELETE 339 // 22 | true | true | true | ADD | ADD? 340 // 23 | true | true | true | DELETE | DELETE 341 // 24 | true | true | true | UPDATE | UPDATE 342 for _, newProbeID := range up.Check.Probes { 343 if newProbeID != probeID { 344 continue 345 } 346 347 if _, found := existingChecks[up.Check.Id]; found { 348 // the probe did already know about this check 349 if up.Check.Enabled { 350 // pass-thru and let the probe handle the operation 351 return &up 352 } 353 354 // if the check is being *disabled*, the 355 // operation needs to be converted to a delete 356 return &sm.CheckChange{ 357 Operation: sm.CheckOperation_CHECK_DELETE, 358 Check: sm.Check{Id: up.Check.Id}, 359 } 360 } 361 362 // the probe did not know about this check before 363 if !up.Check.Enabled { 364 return nil // SKIP 365 } 366 367 switch up.Operation { 368 case sm.CheckOperation_CHECK_DELETE: 369 return nil // SKIP 370 371 case sm.CheckOperation_CHECK_ADD: 372 return &up 373 374 case sm.CheckOperation_CHECK_UPDATE: 375 // updates for listed probes get converted to add operations 376 return &sm.CheckChange{ 377 Operation: sm.CheckOperation_CHECK_ADD, 378 Check: up.Check, 379 } 380 381 default: 382 panic("unhandled operation") 383 } 384 } 385 386 // the probe was *not* found, either because it's not listed (ADD, 387 // DELETE), or because it's being removed (UPDATE) 388 389 if _, found := existingChecks[up.Check.Id]; !found { 390 return nil // SKIP 391 } 392 393 switch up.Operation { 394 case sm.CheckOperation_CHECK_ADD: 395 // the check is being added, it is NOT meant for this 396 // probe, but it DOES exist in the probe: transform it 397 // to a DELETE 398 return &sm.CheckChange{ 399 Operation: sm.CheckOperation_CHECK_DELETE, 400 Check: sm.Check{Id: up.Check.Id}, 401 } 402 403 case sm.CheckOperation_CHECK_DELETE: 404 return &up 405 406 case sm.CheckOperation_CHECK_UPDATE: 407 // updates and deletes for unlisted probes get converted to delete operations 408 return &sm.CheckChange{ 409 Operation: sm.CheckOperation_CHECK_DELETE, 410 Check: sm.Check{Id: up.Check.Id}, 411 } 412 413 default: 414 panic("unhandled operation") 415 } 416 } 417 418 // sendInitialChanges transfers the initial set of changes for the 419 // specified probe and returns the set of changes that were sent to the 420 // probe. 421 func (s *ChecksServer) sendInitialChanges(currentState *sm.ProbeState, stream sm.Checks_GetChangesServer, probeID int64) (checksSet, error) { 422 checks, err := s.db.ListChecksForProbe(stream.Context(), probeID) 423 if err != nil { 424 return nil, err 425 } 426 427 m := make(checksSet) 428 429 if len(checks) == 0 && len(currentState.Checks) == 0 { 430 return m, nil 431 } 432 433 knownChecks := make(map[int64]float64, len(currentState.Checks)) 434 for _, e := range currentState.Checks { 435 knownChecks[e.Id] = e.LastModified 436 } 437 438 var changes []sm.CheckChange 439 440 for _, check := range checks { 441 if !check.Enabled { 442 continue 443 } 444 445 modTime, exists := knownChecks[check.Id] 446 m[check.Id] = struct{}{} 447 448 if exists && modTime == check.Modified { 449 // Skip check already known by probe 450 continue 451 } 452 453 op := sm.CheckOperation_CHECK_ADD 454 if exists { 455 // Update already existing check 456 op = sm.CheckOperation_CHECK_UPDATE 457 } 458 459 changes = append(changes, sm.CheckChange{ 460 Operation: op, 461 Check: check, 462 }) 463 } 464 465 // Delete missing checks previously known by the probe 466 for cid := range knownChecks { 467 if _, found := m[cid]; !found { 468 changes = append(changes, sm.CheckChange{ 469 Operation: sm.CheckOperation_CHECK_DELETE, 470 Check: sm.Check{Id: cid}, 471 }) 472 } 473 } 474 475 s.logger.Info().Int64("probeId", probeID).Interface("changes", changes).Msg("initial changes") 476 477 // If existing checks were provided, notify the probe that we're sending a diff 478 // against the existing checks instead of the full batch. 479 deltaFlag := len(currentState.Checks) > 0 480 if err := stream.Send(&sm.Changes{Checks: changes, IsDeltaFirstBatch: deltaFlag}); err != nil { 481 return nil, fmt.Errorf("sending check changes to probe %d: %w", probeID, err) 482 } 483 484 return m, nil 485 }