blob: 84765520c3dd4a3ef07c8470d1324f89656b78cb [file]
// Copyright 2022 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"context"
"errors"
"fmt"
"time"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
)
type CollectClusterWatchEventsParam struct {
Lg *zap.Logger
Endpoints []string
MaxRevisionChan <-chan int64
ClientSet *ClientSet
}
func CollectClusterWatchEvents(ctx context.Context, param CollectClusterWatchEventsParam) error {
var g errgroup.Group
memberMaxRevisionChans := make([]chan int64, len(param.Endpoints))
for i, endpoint := range param.Endpoints {
memberMaxRevisionChan := make(chan int64, 1)
memberMaxRevisionChans[i] = memberMaxRevisionChan
g.Go(func() error {
c, err := param.ClientSet.NewClient([]string{endpoint})
if err != nil {
return err
}
defer c.Close()
return watchUntilRevision(ctx, param.Lg, c, memberMaxRevisionChan)
})
}
g.Go(func() error {
maxRevision := <-param.MaxRevisionChan
for _, memberChan := range memberMaxRevisionChans {
memberChan <- maxRevision
}
return nil
})
return g.Wait()
}
// watchUntilRevision watches all changes until context is canceled, it has observed the revision provided via maxRevisionChan or maxRevisionChan was closed.
func watchUntilRevision(ctx context.Context, lg *zap.Logger, c *RecordingClient, maxRevisionChan <-chan int64) error {
var maxRevision int64
var lastRevision int64 = 1
var closing bool
ctx, cancel := context.WithCancel(ctx)
defer cancel()
resetWatch:
for {
if closing {
if maxRevision == 0 {
return errors.New("client didn't collect all events, max revision not set")
}
if lastRevision < maxRevision {
return fmt.Errorf("client didn't collect all events, got: %d, expected: %d", lastRevision, maxRevision)
}
return nil
}
watch := c.Watch(ctx, "", lastRevision+1, true, true, false)
for {
select {
case revision, ok := <-maxRevisionChan:
if ok {
maxRevision = revision
if lastRevision >= maxRevision {
closing = true
cancel()
}
} else {
// Only cancel if maxRevision was never set.
if maxRevision == 0 {
closing = true
cancel()
}
}
case <-time.After(100 * time.Millisecond):
c.RequestProgress(ctx)
case resp, ok := <-watch:
if !ok {
lg.Info("Watch channel closed")
continue resetWatch
}
if resp.Err() != nil {
if resp.Canceled {
if resp.CompactRevision > lastRevision {
lastRevision = resp.CompactRevision
}
continue resetWatch
}
return fmt.Errorf("watch stream received error: %w", resp.Err())
}
if len(resp.Events) > 0 {
lastRevision = resp.Events[len(resp.Events)-1].Kv.ModRevision
}
if maxRevision != 0 && lastRevision >= maxRevision {
closing = true
cancel()
}
}
}
}
}