Skip to content

Commit 1ecde18

Browse files
authored
xds: generic xds client ads stream tests (#8307)
1 parent 5c0d552 commit 1ecde18

File tree

12 files changed

+2166
-48
lines changed

12 files changed

+2166
-48
lines changed

xds/internal/clients/internal/testutils/channel.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,28 @@ func (c *Channel) Replace(value any) {
5959
}
6060
}
6161

62+
// SendContext sends value on the underlying channel, or returns an error if
63+
// the context expires.
64+
func (c *Channel) SendContext(ctx context.Context, value any) error {
65+
select {
66+
case c.C <- value:
67+
return nil
68+
case <-ctx.Done():
69+
return ctx.Err()
70+
}
71+
}
72+
73+
// Drain drains the channel by repeatedly reading from it until it is empty.
74+
func (c *Channel) Drain() {
75+
for {
76+
select {
77+
case <-c.C:
78+
default:
79+
return
80+
}
81+
}
82+
}
83+
6284
// NewChannelWithSize returns a new Channel with a buffer of bufSize.
6385
func NewChannelWithSize(bufSize int) *Channel {
6486
return &Channel{C: make(chan any, bufSize)}

xds/internal/clients/xdsclient/ads_stream.go

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -73,39 +73,13 @@ type adsStreamEventHandler interface {
7373
onResponse(response, func()) ([]string, error) // Called when a response is received on the ADS stream.
7474
}
7575

76-
// watchState is a enum that describes the watch state of a particular
77-
// resource.
78-
type watchState int
79-
80-
const (
81-
// resourceWatchStateStarted is the state where a watch for a resource was
82-
// started, but a request asking for that resource is yet to be sent to the
83-
// management server.
84-
resourceWatchStateStarted watchState = iota
85-
// resourceWatchStateRequested is the state when a request has been sent for
86-
// the resource being watched.
87-
resourceWatchStateRequested
88-
// ResourceWatchStateReceived is the state when a response has been received
89-
// for the resource being watched.
90-
resourceWatchStateReceived
91-
// resourceWatchStateTimeout is the state when the watch timer associated
92-
// with the resource expired because no response was received.
93-
resourceWatchStateTimeout
94-
)
95-
96-
// resourceWatchState is the state corresponding to a resource being watched.
97-
type resourceWatchState struct {
98-
State watchState // Watch state of the resource.
99-
ExpiryTimer *time.Timer // Timer for the expiry of the watch.
100-
}
101-
10276
// state corresponding to a resource type.
10377
type resourceTypeState struct {
104-
version string // Last acked version. Should not be reset when the stream breaks.
105-
nonce string // Last received nonce. Should be reset when the stream breaks.
106-
bufferedRequests chan struct{} // Channel to buffer requests when writing is blocked.
107-
subscribedResources map[string]*resourceWatchState // Map of subscribed resource names to their state.
108-
pendingWrite bool // True if there is a pending write for this resource type.
78+
version string // Last acked version. Should not be reset when the stream breaks.
79+
nonce string // Last received nonce. Should be reset when the stream breaks.
80+
bufferedRequests chan struct{} // Channel to buffer requests when writing is blocked.
81+
subscribedResources map[string]*xdsresource.ResourceWatchState // Map of subscribed resource names to their state.
82+
pendingWrite bool // True if there is a pending write for this resource type.
10983
}
11084

11185
// adsStreamImpl provides the functionality associated with an ADS (Aggregated
@@ -198,15 +172,15 @@ func (s *adsStreamImpl) subscribe(typ ResourceType, name string) {
198172
// An entry in the type state map is created as part of the first
199173
// subscription request for this type.
200174
state = &resourceTypeState{
201-
subscribedResources: make(map[string]*resourceWatchState),
175+
subscribedResources: make(map[string]*xdsresource.ResourceWatchState),
202176
bufferedRequests: make(chan struct{}, 1),
203177
}
204178
s.resourceTypeState[typ] = state
205179
}
206180

207181
// Create state for the newly subscribed resource. The watch timer will
208182
// be started when a request for this resource is actually sent out.
209-
state.subscribedResources[name] = &resourceWatchState{State: resourceWatchStateStarted}
183+
state.subscribedResources[name] = &xdsresource.ResourceWatchState{State: xdsresource.ResourceWatchStateStarted}
210184
state.pendingWrite = true
211185

212186
// Send a request for the resource type with updated subscriptions.
@@ -616,8 +590,8 @@ func (s *adsStreamImpl) onRecv(stream clients.Stream, names []string, url, versi
616590
s.logger.Warningf("ADS stream received a response for resource %q, but no state exists for it", name)
617591
continue
618592
}
619-
if ws := rs.State; ws == resourceWatchStateStarted || ws == resourceWatchStateRequested {
620-
rs.State = resourceWatchStateReceived
593+
if ws := rs.State; ws == xdsresource.ResourceWatchStateStarted || ws == xdsresource.ResourceWatchStateRequested {
594+
rs.State = xdsresource.ResourceWatchStateReceived
621595
if rs.ExpiryTimer != nil {
622596
rs.ExpiryTimer.Stop()
623597
rs.ExpiryTimer = nil
@@ -652,14 +626,14 @@ func (s *adsStreamImpl) onError(err error, msgReceived bool) {
652626
s.mu.Lock()
653627
for _, state := range s.resourceTypeState {
654628
for _, rs := range state.subscribedResources {
655-
if rs.State != resourceWatchStateRequested {
629+
if rs.State != xdsresource.ResourceWatchStateRequested {
656630
continue
657631
}
658632
if rs.ExpiryTimer != nil {
659633
rs.ExpiryTimer.Stop()
660634
rs.ExpiryTimer = nil
661635
}
662-
rs.State = resourceWatchStateStarted
636+
rs.State = xdsresource.ResourceWatchStateStarted
663637
}
664638
}
665639
s.mu.Unlock()
@@ -691,23 +665,38 @@ func (s *adsStreamImpl) startWatchTimersLocked(typ ResourceType, names []string)
691665
if !ok {
692666
continue
693667
}
694-
if resourceState.State != resourceWatchStateStarted {
668+
if resourceState.State != xdsresource.ResourceWatchStateStarted {
695669
continue
696670
}
697-
resourceState.State = resourceWatchStateRequested
671+
resourceState.State = xdsresource.ResourceWatchStateRequested
698672

699673
rs := resourceState
700674
resourceState.ExpiryTimer = time.AfterFunc(s.watchExpiryTimeout, func() {
701675
s.mu.Lock()
702-
rs.State = resourceWatchStateTimeout
676+
rs.State = xdsresource.ResourceWatchStateTimeout
703677
rs.ExpiryTimer = nil
704678
s.mu.Unlock()
705679
s.eventHandler.onWatchExpiry(typ, name)
706680
})
707681
}
708682
}
709683

710-
func resourceNames(m map[string]*resourceWatchState) []string {
684+
func (s *adsStreamImpl) adsResourceWatchStateForTesting(rType ResourceType, resourceName string) (xdsresource.ResourceWatchState, error) {
685+
s.mu.Lock()
686+
defer s.mu.Unlock()
687+
688+
state, ok := s.resourceTypeState[rType]
689+
if !ok {
690+
return xdsresource.ResourceWatchState{}, fmt.Errorf("unknown resource type: %v", rType)
691+
}
692+
resourceState, ok := state.subscribedResources[resourceName]
693+
if !ok {
694+
return xdsresource.ResourceWatchState{}, fmt.Errorf("unknown resource name: %v", resourceName)
695+
}
696+
return *resourceState, nil
697+
}
698+
699+
func resourceNames(m map[string]*xdsresource.ResourceWatchState) []string {
711700
ret := make([]string, len(m))
712701
idx := 0
713702
for name := range m {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
*
3+
* Copyright 2025 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
// Package internal contains functionality internal to the xdsclient package.
19+
package internal
20+
21+
import "time"
22+
23+
var (
24+
// WatchExpiryTimeout is the watch expiry timeout for xDS client. It can be
25+
// overridden by tests to change the default watch expiry timeout.
26+
WatchExpiryTimeout time.Duration
27+
28+
// StreamBackoff is the stream backoff for xDS client. It can be overridden
29+
// by tests to change the default backoff strategy.
30+
StreamBackoff func(int) time.Duration
31+
32+
// ResourceWatchStateForTesting gets the watch state for the resource
33+
// identified by the given resource type and resource name. Returns a
34+
// non-nil error if there is no such resource being watched.
35+
ResourceWatchStateForTesting any // func(*xdsclient.XDSClient, xdsclient.ResourceType, string) error
36+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
*
3+
* Copyright 2021 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package xdsresource
19+
20+
import "time"
21+
22+
// WatchState is a enum that describes the watch state of a particular
23+
// resource.
24+
type WatchState int
25+
26+
const (
27+
// ResourceWatchStateStarted is the state where a watch for a resource was
28+
// started, but a request asking for that resource is yet to be sent to the
29+
// management server.
30+
ResourceWatchStateStarted WatchState = iota
31+
// ResourceWatchStateRequested is the state when a request has been sent for
32+
// the resource being watched.
33+
ResourceWatchStateRequested
34+
// ResourceWatchStateReceived is the state when a response has been received
35+
// for the resource being watched.
36+
ResourceWatchStateReceived
37+
// ResourceWatchStateTimeout is the state when the watch timer associated
38+
// with the resource expired because no response was received.
39+
ResourceWatchStateTimeout
40+
)
41+
42+
// ResourceWatchState is the state corresponding to a resource being watched.
43+
type ResourceWatchState struct {
44+
State WatchState // Watch state of the resource.
45+
ExpiryTimer *time.Timer // Timer for the expiry of the watch.
46+
}

0 commit comments

Comments
 (0)