github.com/cloudwan/edgelq-sdk@v1.15.4/iam/access/v1alpha2/group/group.pb.query_watcher.go (about) 1 // Code generated by protoc-gen-goten-access 2 // Resource: Group 3 // DO NOT EDIT!!! 4 5 package group_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 group_client "github.com/cloudwan/edgelq-sdk/iam/client/v1alpha2/group" 21 group "github.com/cloudwan/edgelq-sdk/iam/resources/v1alpha2/group" 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 group_client.GroupServiceClient 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 *group.ParentName 41 Filter *group.Filter 42 View view.View 43 FieldMask *group.Group_FieldMask 44 OrderBy *group.OrderBy 45 Cursor *group.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 []*group.GroupChange 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 group.GroupChangeList(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 group_client.GroupServiceClient, 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 group_client.GroupServiceClient, 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", "group-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.WatchGroups(ctx, &group_client.WatchGroupsRequest{ 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([]*group.GroupChange, 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.GetGroupChanges()...) 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.GetGroupChanges(), 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(group.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.(group.GroupChangeList), 295 Reset: isReset, 296 LostSync: isLostSync, 297 InSync: isCurrent, 298 SnapshotSize: snapshotSize, 299 CheckSize: snapshotSize >= 0, 300 } 301 }, 302 ) 303 304 gotenaccess.GetRegistry().RegisterQueryWatcherConstructor(group.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.(*group.Group_FieldMask) 317 } 318 if params.OrderBy != nil { 319 cfg.OrderBy = params.OrderBy.(*group.OrderBy) 320 } 321 if params.Cursor != nil { 322 cfg.Cursor = params.Cursor.(*group.PagerCursor) 323 } 324 if params.Parent != nil { 325 cfg.Parent = params.Parent.(*group.ParentName) 326 } 327 if params.Filter != nil { 328 cfg.Filter = params.Filter.(*group.Filter) 329 } 330 return NewQueryWatcherWithIChan(id, group_client.NewGroupServiceClient(cc), cfg, ch) 331 }) 332 }