/* * * Copyright 2020 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 resolver_test import ( "context" "testing" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "github.com/google/uuid" "google.golang.org/grpc/internal/testutils" "google.golang.org/grpc/internal/testutils/xds/e2e" "google.golang.org/grpc/resolver" "google.golang.org/protobuf/types/known/wrapperspb" v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" ) // Tests the case where the listener resource starts pointing to a new route // configuration resource after the xDS resolver has successfully resolved the // service name and pushed an update on the channel. The test verifies that the // resolver stops requesting the old route configuration resource and requests // the new resource, and once successfully resolved, verifies that the update // from the resolver matches expected service config. func (s) TestServiceWatch_ListenerPointsToNewRouteConfiguration(t *testing.T) { // Spin up an xDS management server for the test. ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() nodeID := uuid.New().String() mgmtServer, lisCh, routeCfgCh, bc := setupManagementServerForTest(t, nodeID) // Configure resources on the management server. resources := e2e.DefaultClientResources(e2e.ResourceParams{ DialTarget: defaultTestServiceName, NodeID: nodeID, Host: defaultTestHostname, Port: defaultTestPort[0], SecLevel: e2e.SecurityLevelNone, }) if err := mgmtServer.Update(ctx, resources); err != nil { t.Fatal(err) } stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc) // Verify initial update from the resolver. waitForResourceNames(ctx, t, lisCh, []string{defaultTestServiceName}) waitForResourceNames(ctx, t, routeCfgCh, []string{resources.Routes[0].Name}) verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name)) // Update the listener resource to point to a new route configuration name. // The old route configuration resource is left unchanged to prevent a race: // if it were removed immediately, the resolver might encounter a // "resource-not-found" error for the old route before the listener update // successfully transitions the client to the new route. newTestRouteConfigName := defaultTestRouteConfigName + "-new" resources.Listeners = []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, newTestRouteConfigName)} resources.SkipValidation = true mgmtServer.Update(ctx, resources) // Verify that the new route configuration resource is requested. waitForResourceNames(ctx, t, routeCfgCh, []string{newTestRouteConfigName}) // Update the old route configuration resource by adding a new route. resources.Routes[0].VirtualHosts[0].Routes = append(resources.Routes[0].VirtualHosts[0].Routes, &v3routepb.Route{ Match: &v3routepb.RouteMatch{ PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/foo/bar"}, CaseSensitive: &wrapperspb.BoolValue{Value: false}, }, Action: &v3routepb.Route_Route{ Route: &v3routepb.RouteAction{ ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: "some-random-cluster"}, }, }, }) mgmtServer.Update(ctx, resources) // Wait for no update from the resolver since the listener resource no // longer points to the old route resource and new route resource hasn't // been sent yet. verifyNoUpdateFromResolver(ctx, t, stateCh) // Update the management server with the new route configuration resource. resources.Routes = append(resources.Routes, e2e.DefaultRouteConfig(newTestRouteConfigName, defaultTestServiceName, resources.Clusters[0].Name)) configureResources(ctx, t, mgmtServer, nodeID, resources.Listeners, resources.Routes, nil, nil) // Ensure update from the resolver. verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name)) } // Tests the case where the listener resource changes to contain an inline route // configuration and changes back to having a route configuration resource name. // Verifies that the expected xDS resource names are requested by the resolver // and that the update from the resolver matches expected service config. func (s) TestServiceWatch_ListenerPointsToInlineRouteConfiguration(t *testing.T) { // Spin up an xDS management server for the test. ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() nodeID := uuid.New().String() mgmtServer, lisCh, routeCfgCh, bc := setupManagementServerForTest(t, nodeID) // Configure resources on the management server. resources := e2e.DefaultClientResources(e2e.ResourceParams{ DialTarget: defaultTestServiceName, NodeID: nodeID, Host: defaultTestHostname, Port: defaultTestPort[0], SecLevel: e2e.SecurityLevelNone, }) if err := mgmtServer.Update(ctx, resources); err != nil { t.Fatal(err) } stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc) // Verify initial update from the resolver. waitForResourceNames(ctx, t, lisCh, []string{defaultTestServiceName}) waitForResourceNames(ctx, t, routeCfgCh, []string{resources.Routes[0].Name}) verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name)) // Update listener to contain an inline route configuration. hcm := testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{ RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ RouteConfig: &v3routepb.RouteConfiguration{ Name: resources.Routes[0].Name, VirtualHosts: []*v3routepb.VirtualHost{{ Domains: []string{defaultTestServiceName}, Routes: []*v3routepb.Route{{ Match: &v3routepb.RouteMatch{ PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}, }, Action: &v3routepb.Route_Route{ Route: &v3routepb.RouteAction{ ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: resources.Clusters[0].Name}, }, }, }}, }}, }, }, HttpFilters: []*v3httppb.HttpFilter{e2e.HTTPFilter("router", &v3routerpb.Router{})}, }) resources.Listeners = []*v3listenerpb.Listener{{ Name: defaultTestServiceName, ApiListener: &v3listenerpb.ApiListener{ApiListener: hcm}, FilterChains: []*v3listenerpb.FilterChain{{ Name: "filter-chain-name", Filters: []*v3listenerpb.Filter{{ Name: wellknown.HTTPConnectionManager, ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: hcm}, }}, }}, }} configureResources(ctx, t, mgmtServer, nodeID, resources.Listeners, nil, nil, nil) // Verify that the old route configuration is not requested anymore. waitForResourceNames(ctx, t, routeCfgCh, []string{}) verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name)) // Update listener back to contain a route configuration name. resources.Listeners = []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, resources.Routes[0].Name)} configureResources(ctx, t, mgmtServer, nodeID, resources.Listeners, resources.Routes, nil, nil) // Verify that that route configuration resource is requested. waitForResourceNames(ctx, t, routeCfgCh, []string{resources.Routes[0].Name}) // Verify that appropriate SC is pushed on the channel. verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name)) }