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  }