github.com/cloudwan/edgelq-sdk@v1.15.4/audit/access/v1alpha2/method_descriptor/method_descriptor.pb.query_watcher.go (about)

     1  // Code generated by protoc-gen-goten-access
     2  // Resource: MethodDescriptor
     3  // DO NOT EDIT!!!
     4  
     5  package method_descriptor_access
     6  
     7  import (
     8  	"context"
     9  	"time"
    10  
    11  	"google.golang.org/grpc"
    12  	"google.golang.org/protobuf/types/known/timestamppb"
    13  
    14  	gotenaccess "github.com/cloudwan/goten-sdk/runtime/access"
    15  	gotenobservability "github.com/cloudwan/goten-sdk/runtime/observability"
    16  	gotenresource "github.com/cloudwan/goten-sdk/runtime/resource"
    17  	"github.com/cloudwan/goten-sdk/types/view"
    18  	"github.com/cloudwan/goten-sdk/types/watch_type"
    19  
    20  	method_descriptor_client "github.com/cloudwan/edgelq-sdk/audit/client/v1alpha2/method_descriptor"
    21  	method_descriptor "github.com/cloudwan/edgelq-sdk/audit/resources/v1alpha2/method_descriptor"
    22  )
    23  
    24  // QueryWatcher is a low-level, stateless watcher.
    25  // Initial updates are sent in chunks. Once snapshot is complete, further
    26  // changes are incremental - unless Reset flag is set, in which case another
    27  // snapshot is received.
    28  type QueryWatcher struct {
    29  	client       method_descriptor_client.MethodDescriptorServiceClient
    30  	params       *QueryWatcherParams
    31  	syncDeadline time.Time
    32  	identifier   int
    33  	evtsChan     chan *QueryWatcherEvent
    34  	iEvtsChan    chan gotenaccess.QueryWatcherEvent
    35  	resumeToken  string
    36  	startingTime *timestamppb.Timestamp
    37  }
    38  
    39  type QueryWatcherParams struct {
    40  	Filter       *method_descriptor.Filter
    41  	View         view.View
    42  	FieldMask    *method_descriptor.MethodDescriptor_FieldMask
    43  	OrderBy      *method_descriptor.OrderBy
    44  	Cursor       *method_descriptor.PagerCursor
    45  	ChunkSize    int
    46  	PageSize     int
    47  	WatchType    watch_type.WatchType
    48  	StartingTime *timestamppb.Timestamp
    49  
    50  	RecoveryDeadline time.Duration
    51  	RetryTimeout     time.Duration
    52  }
    53  
    54  type QueryWatcherEvent struct {
    55  	Identifier   int
    56  	Changes      []*method_descriptor.MethodDescriptorChange
    57  	Reset        bool
    58  	LostSync     bool
    59  	InSync       bool
    60  	SnapshotSize int64
    61  	CheckSize    bool
    62  }
    63  
    64  func (e *QueryWatcherEvent) GetWatcherIdentifier() int {
    65  	return e.Identifier
    66  }
    67  
    68  func (e *QueryWatcherEvent) GetChanges() gotenresource.ResourceChangeList {
    69  	return method_descriptor.MethodDescriptorChangeList(e.Changes)
    70  }
    71  
    72  func (e *QueryWatcherEvent) IsReset() bool {
    73  	return e.Reset
    74  }
    75  
    76  func (e *QueryWatcherEvent) IsLostSync() bool {
    77  	return e.LostSync
    78  }
    79  
    80  func (e *QueryWatcherEvent) IsSync() bool {
    81  	return e.InSync
    82  }
    83  
    84  func (e *QueryWatcherEvent) GetSnapshotSize() int64 {
    85  	return e.SnapshotSize
    86  }
    87  
    88  func (e *QueryWatcherEvent) HasSnapshotSize() bool {
    89  	return e.CheckSize
    90  }
    91  
    92  func NewQueryWatcher(id int, client method_descriptor_client.MethodDescriptorServiceClient,
    93  	params *QueryWatcherParams, evtsChan chan *QueryWatcherEvent) *QueryWatcher {
    94  	return &QueryWatcher{
    95  		client:       client,
    96  		params:       params,
    97  		identifier:   id,
    98  		evtsChan:     evtsChan,
    99  		startingTime: params.StartingTime,
   100  	}
   101  }
   102  
   103  func NewQueryWatcherWithIChan(id int, client method_descriptor_client.MethodDescriptorServiceClient,
   104  	params *QueryWatcherParams, evtsChan chan gotenaccess.QueryWatcherEvent) *QueryWatcher {
   105  	return &QueryWatcher{
   106  		client:       client,
   107  		params:       params,
   108  		identifier:   id,
   109  		iEvtsChan:    evtsChan,
   110  		startingTime: params.StartingTime,
   111  	}
   112  }
   113  
   114  func (qw *QueryWatcher) QueryWatcher() {}
   115  
   116  func (qw *QueryWatcher) Run(ctx context.Context) error {
   117  	log := gotenobservability.LoggerFromContext(ctx).
   118  		WithField("query-watcher", "methodDescriptor-query-watcher").
   119  		WithField("query-filter", qw.params.Filter.String()).
   120  		WithField("query-order-by", qw.params.OrderBy.String()).
   121  		WithField("query-cursor", qw.params.Cursor.String())
   122  	ctx = gotenobservability.LoggerToContext(ctx, log)
   123  
   124  	log.Infof("Running new query")
   125  	inSync := false
   126  	skipErrorBackoff := false
   127  	for {
   128  		stream, err := qw.client.WatchMethodDescriptors(ctx, &method_descriptor_client.WatchMethodDescriptorsRequest{
   129  			Type:         qw.params.WatchType,
   130  			Filter:       qw.params.Filter,
   131  			View:         qw.params.View,
   132  			FieldMask:    qw.params.FieldMask,
   133  			MaxChunkSize: int32(qw.params.ChunkSize),
   134  			OrderBy:      qw.params.OrderBy,
   135  			ResumeToken:  qw.resumeToken,
   136  			PageSize:     int32(qw.params.PageSize),
   137  			PageToken:    qw.params.Cursor,
   138  			StartingTime: qw.startingTime,
   139  		})
   140  
   141  		if err != nil {
   142  			if ctx.Err() == nil {
   143  				log.WithError(err).Warnf("watch initialization error")
   144  			}
   145  		} else {
   146  			pending := make([]*method_descriptor.MethodDescriptorChange, 0)
   147  			for {
   148  				resp, err := stream.Recv()
   149  				if err != nil {
   150  					if ctx.Err() == nil {
   151  						log.WithError(err).Warnf("watch error")
   152  					}
   153  					break
   154  				} else {
   155  					var outputEvt *QueryWatcherEvent
   156  
   157  					// until we reach initial sync, we will send all the data as we get to minimize
   158  					// potential impact on memory (if receiver does not need state). Later on, we will
   159  					// collect changes and send once IsCurrent flag is sent. This is to handle soft reset
   160  					// flag. Changes after initial sync are however practically always small.
   161  					skipErrorBackoff = true
   162  					if inSync {
   163  						pending = append(pending, resp.GetMethodDescriptorChanges()...)
   164  						if resp.IsSoftReset {
   165  							log.Debugf("received soft reset after %d changes", len(pending))
   166  							pending = nil
   167  						} else if resp.IsHardReset {
   168  							log.Warnf("received hard reset after %d changes", len(pending))
   169  
   170  							qw.resumeToken = ""
   171  							inSync = false
   172  							pending = nil
   173  							outputEvt = &QueryWatcherEvent{
   174  								Identifier: qw.identifier,
   175  								Reset:      true,
   176  							}
   177  						} else if resp.GetSnapshotSize() >= 0 {
   178  							log.Debugf("received snapshot size info: %d", resp.GetSnapshotSize())
   179  
   180  							outputEvt = &QueryWatcherEvent{
   181  								Identifier:   qw.identifier,
   182  								SnapshotSize: resp.GetSnapshotSize(),
   183  								CheckSize:    true,
   184  								Changes:      pending,
   185  								InSync:       true,
   186  							}
   187  						} else if resp.GetIsCurrent() {
   188  							qw.syncDeadline = time.Time{}
   189  							if resp.GetResumeToken() != "" {
   190  								qw.resumeToken = resp.GetResumeToken()
   191  								qw.startingTime = nil
   192  							}
   193  							if len(pending) > 0 {
   194  								outputEvt = &QueryWatcherEvent{
   195  									Identifier: qw.identifier,
   196  									Changes:    pending,
   197  									InSync:     true,
   198  								}
   199  							}
   200  							pending = nil
   201  						}
   202  					} else {
   203  						if resp.IsCurrent {
   204  							log.Infof("query synchronized")
   205  							inSync = true
   206  							qw.syncDeadline = time.Time{}
   207  							if resp.GetResumeToken() != "" {
   208  								qw.resumeToken = resp.GetResumeToken()
   209  								qw.startingTime = nil
   210  							}
   211  						}
   212  						outputEvt = &QueryWatcherEvent{
   213  							Identifier:   qw.identifier,
   214  							Changes:      resp.GetMethodDescriptorChanges(),
   215  							SnapshotSize: resp.SnapshotSize,
   216  							Reset:        resp.IsHardReset || resp.IsSoftReset,
   217  							InSync:       inSync,
   218  							CheckSize:    resp.SnapshotSize >= 0,
   219  						}
   220  					}
   221  					if outputEvt != nil {
   222  						qw.sendEvt(ctx, outputEvt)
   223  					}
   224  				}
   225  			}
   226  		}
   227  
   228  		if ctx.Err() != nil {
   229  			return ctx.Err()
   230  		}
   231  
   232  		// if we disconnected during initial snapshot (we were not in sync), send a message to cancel all data
   233  		if !inSync {
   234  			evt := &QueryWatcherEvent{
   235  				Identifier: qw.identifier,
   236  				Reset:      true,
   237  			}
   238  			qw.sendEvt(ctx, evt)
   239  		}
   240  		if qw.syncDeadline.IsZero() && qw.params.RecoveryDeadline > 0 {
   241  			qw.syncDeadline = time.Now().UTC().Add(qw.params.RecoveryDeadline)
   242  			log.Infof("lost sync, scheduling recovery with timeout %s", qw.syncDeadline)
   243  		} else if !qw.syncDeadline.IsZero() && time.Now().UTC().After(qw.syncDeadline) {
   244  			log.Errorf("could not recover within %s, reporting lost sync", qw.syncDeadline)
   245  			evt := &QueryWatcherEvent{
   246  				Identifier: qw.identifier,
   247  				LostSync:   true,
   248  				Reset:      true,
   249  			}
   250  			qw.resumeToken = ""
   251  			inSync = false
   252  			qw.sendEvt(ctx, evt)
   253  		}
   254  
   255  		// If we had working watch, dont sleep on first disconnection, we are likely to be able to
   256  		// reconnect quickly and then we dont want to miss updates
   257  		if !skipErrorBackoff {
   258  			backoff := time.After(qw.params.RetryTimeout)
   259  			select {
   260  			case <-backoff:
   261  				log.Debugf("after backoff %s", qw.params.RetryTimeout)
   262  			case <-ctx.Done():
   263  				log.Debugf("context done, reason: %s", ctx.Err())
   264  				return ctx.Err()
   265  			}
   266  		} else {
   267  			skipErrorBackoff = false
   268  		}
   269  	}
   270  }
   271  
   272  func (qw *QueryWatcher) sendEvt(ctx context.Context, evt *QueryWatcherEvent) {
   273  	if qw.evtsChan != nil {
   274  		select {
   275  		case <-ctx.Done():
   276  		case qw.evtsChan <- evt:
   277  		}
   278  	} else {
   279  		select {
   280  		case <-ctx.Done():
   281  		case qw.iEvtsChan <- evt:
   282  		}
   283  	}
   284  }
   285  
   286  func init() {
   287  	gotenaccess.GetRegistry().RegisterQueryWatcherEventConstructor(method_descriptor.GetDescriptor(),
   288  		func(evtId int, changes gotenresource.ResourceChangeList, isReset, isLostSync, isCurrent bool, snapshotSize int64) gotenaccess.QueryWatcherEvent {
   289  			return &QueryWatcherEvent{
   290  				Identifier:   evtId,
   291  				Changes:      changes.(method_descriptor.MethodDescriptorChangeList),
   292  				Reset:        isReset,
   293  				LostSync:     isLostSync,
   294  				InSync:       isCurrent,
   295  				SnapshotSize: snapshotSize,
   296  				CheckSize:    snapshotSize >= 0,
   297  			}
   298  		},
   299  	)
   300  
   301  	gotenaccess.GetRegistry().RegisterQueryWatcherConstructor(method_descriptor.GetDescriptor(), func(id int, cc grpc.ClientConnInterface,
   302  		params *gotenaccess.QueryWatcherConfigParams, ch chan gotenaccess.QueryWatcherEvent) gotenaccess.QueryWatcher {
   303  		cfg := &QueryWatcherParams{
   304  			WatchType:        params.WatchType,
   305  			View:             params.View,
   306  			ChunkSize:        params.ChunkSize,
   307  			PageSize:         params.PageSize,
   308  			StartingTime:     params.StartingTime,
   309  			RecoveryDeadline: params.RecoveryDeadline,
   310  			RetryTimeout:     params.RetryTimeout,
   311  		}
   312  		if params.FieldMask != nil {
   313  			cfg.FieldMask = params.FieldMask.(*method_descriptor.MethodDescriptor_FieldMask)
   314  		}
   315  		if params.OrderBy != nil {
   316  			cfg.OrderBy = params.OrderBy.(*method_descriptor.OrderBy)
   317  		}
   318  		if params.Cursor != nil {
   319  			cfg.Cursor = params.Cursor.(*method_descriptor.PagerCursor)
   320  		}
   321  		if params.Filter != nil {
   322  			cfg.Filter = params.Filter.(*method_descriptor.Filter)
   323  		}
   324  		return NewQueryWatcherWithIChan(id, method_descriptor_client.NewMethodDescriptorServiceClient(cc), cfg, ch)
   325  	})
   326  }