New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
xdsclient: switch xdsclient watch deadlock test to e2e style #5697
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/* | ||
* | ||
* Copyright 2022 gRPC 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 e2e_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"google.golang.org/grpc/internal/testutils" | ||
"google.golang.org/grpc/internal/testutils/xds/e2e" | ||
"google.golang.org/grpc/xds/internal/xdsclient" | ||
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource" | ||
|
||
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" | ||
) | ||
|
||
// TestWatchCallAnotherWatch covers the case where watch() is called inline by a | ||
// callback. It makes sure it doesn't cause a deadlock. | ||
func (s) TestWatchCallAnotherWatch(t *testing.T) { | ||
overrideFedEnvVar(t) | ||
|
||
// Start an xDS management server and set the option to allow it to respond | ||
// to request which only specify a subset of the configured resources. | ||
mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, &e2e.ManagementServerOptions{AllowResourceSubset: true}) | ||
defer cleanup() | ||
|
||
// Create an xDS client with the above bootstrap contents. | ||
client, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) | ||
if err != nil { | ||
t.Fatalf("Failed to create xDS client: %v", err) | ||
} | ||
defer client.Close() | ||
|
||
// Configure the management server to with route configuration resources. | ||
resources := e2e.UpdateOptions{ | ||
NodeID: nodeID, | ||
Routes: []*v3routepb.RouteConfiguration{ | ||
e2e.DefaultRouteConfig(rdsName, ldsName, cdsName), | ||
e2e.DefaultRouteConfig(rdsNameNewStyle, ldsNameNewStyle, cdsName), | ||
}, | ||
SkipValidation: true, | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
defer cancel() | ||
if err := mgmtServer.Update(ctx, resources); err != nil { | ||
t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) | ||
} | ||
|
||
// Start a watch for one route configuration resource. From the watch | ||
// callback of the first resource, register two more watches (one for the | ||
// same resource name, which would be satisfied from the cache, and another | ||
// for a different resource name, which would be satisfied from the server). | ||
updateCh1 := testutils.NewChannel() | ||
updateCh2 := testutils.NewChannel() | ||
updateCh3 := testutils.NewChannel() | ||
var rdsCancel2, rdsCancel3 func() | ||
rdsCancel1 := client.WatchRouteConfig(rdsName, func(u xdsresource.RouteConfigUpdate, err error) { | ||
updateCh1.Send(xdsresource.RouteConfigUpdateErrTuple{Update: u, Err: err}) | ||
// Watch for the same resource name. | ||
rdsCancel2 = client.WatchRouteConfig(rdsName, func(u xdsresource.RouteConfigUpdate, err error) { | ||
updateCh2.Send(xdsresource.RouteConfigUpdateErrTuple{Update: u, Err: err}) | ||
}) | ||
// Watch for a different resource name. | ||
rdsCancel3 = client.WatchRouteConfig(rdsNameNewStyle, func(u xdsresource.RouteConfigUpdate, err error) { | ||
updateCh3.Send(xdsresource.RouteConfigUpdateErrTuple{Update: u, Err: err}) | ||
}) | ||
}) | ||
defer rdsCancel1() | ||
defer func() { | ||
if rdsCancel2 != nil { | ||
rdsCancel2() | ||
} | ||
if rdsCancel3 != nil { | ||
rdsCancel3() | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this not race with the setting of these variables (in the event of an error)? I.e. if you make watch #2 on Do we even need this defer? Can we cancel the watch inside the callback instead after doing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. Moved these to use |
||
}() | ||
|
||
// Verify the contents of the received update for the all watchers. | ||
wantUpdate12 := xdsresource.RouteConfigUpdateErrTuple{ | ||
Update: xdsresource.RouteConfigUpdate{ | ||
VirtualHosts: []*xdsresource.VirtualHost{ | ||
{ | ||
Domains: []string{ldsName}, | ||
Routes: []*xdsresource.Route{ | ||
{ | ||
Prefix: newStringP("/"), | ||
ActionType: xdsresource.RouteActionRoute, | ||
WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 1}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
wantUpdate3 := xdsresource.RouteConfigUpdateErrTuple{ | ||
Update: xdsresource.RouteConfigUpdate{ | ||
VirtualHosts: []*xdsresource.VirtualHost{ | ||
{ | ||
Domains: []string{ldsNameNewStyle}, | ||
Routes: []*xdsresource.Route{ | ||
{ | ||
Prefix: newStringP("/"), | ||
ActionType: xdsresource.RouteActionRoute, | ||
WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 1}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
if err := verifyRouteConfigUpdate(ctx, updateCh1, wantUpdate12); err != nil { | ||
t.Fatal(err) | ||
} | ||
if err := verifyRouteConfigUpdate(ctx, updateCh2, wantUpdate12); err != nil { | ||
t.Fatal(err) | ||
} | ||
if err := verifyRouteConfigUpdate(ctx, updateCh3, wantUpdate3); err != nil { | ||
t.Fatal(err) | ||
} | ||
rdsCancel2() | ||
rdsCancel3() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait := opts == nil || !opts.AllowResourceSubset
Or can we make this a required parameter (i.e. not a pointer) to simplify?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the callsites to
StartManagementServer
are happy with the defaults and therefore they dont pass anything to this function. So, making this not a pointer will change most callsites from passingnil
to an empty struct.I changed it to the single line alternative you mentioned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's not used extensively, it might be worth changing before it's used even more often. nil checks can make the code more complicated and there isn't much benefit to passing a pointer instead of a value here.