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 }