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

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