
This object will be used by a higher level xdsClient object, which will provide the watch API used by the xds resolver and balancer implementations.
145 lines
5.0 KiB
Go
145 lines
5.0 KiB
Go
/*
|
|
*
|
|
* Copyright 2019 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 client
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
"google.golang.org/grpc/grpclog"
|
|
|
|
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
)
|
|
|
|
// newRDSRequest generates an RDS request proto for the provided routeName, to
|
|
// be sent out on the wire.
|
|
func (v2c *v2Client) newRDSRequest(routeName []string) *xdspb.DiscoveryRequest {
|
|
return &xdspb.DiscoveryRequest{
|
|
Node: v2c.nodeProto,
|
|
TypeUrl: routeURL,
|
|
ResourceNames: routeName,
|
|
}
|
|
}
|
|
|
|
// sendRDS sends an RDS request for provided routeName on the provided stream.
|
|
func (v2c *v2Client) sendRDS(stream adsStream, routeName []string) bool {
|
|
if err := stream.Send(v2c.newRDSRequest(routeName)); err != nil {
|
|
grpclog.Infof("xds: RDS request for resource %v failed: %v", routeName, err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// handleRDSResponse processes an RDS response received from the xDS server. On
|
|
// receipt of a good response, it caches validated resources and also invokes
|
|
// the registered watcher callback.
|
|
func (v2c *v2Client) handleRDSResponse(resp *xdspb.DiscoveryResponse) error {
|
|
v2c.mu.Lock()
|
|
defer v2c.mu.Unlock()
|
|
|
|
if v2c.watchMap[ldsResource] == nil {
|
|
return fmt.Errorf("xds: unexpected RDS response when no LDS watcher is registered: %+v", resp)
|
|
}
|
|
target := v2c.watchMap[ldsResource].target[0]
|
|
|
|
wi := v2c.watchMap[rdsResource]
|
|
if wi == nil {
|
|
return fmt.Errorf("xds: no RDS watcher found when handling RDS response: %+v", resp)
|
|
}
|
|
|
|
returnCluster := ""
|
|
localCache := make(map[string]string)
|
|
for _, r := range resp.GetResources() {
|
|
var resource ptypes.DynamicAny
|
|
if err := ptypes.UnmarshalAny(r, &resource); err != nil {
|
|
return fmt.Errorf("xds: failed to unmarshal resource in RDS response: %v", err)
|
|
}
|
|
rc, ok := resource.Message.(*xdspb.RouteConfiguration)
|
|
if !ok {
|
|
return fmt.Errorf("xds: unexpected resource type: %T in RDS response", resource.Message)
|
|
}
|
|
cluster := getClusterFromRouteConfiguration(rc, target)
|
|
if cluster == "" {
|
|
return fmt.Errorf("xds: received invalid RouteConfiguration in RDS response: %+v", rc)
|
|
}
|
|
|
|
// If we get here, it means that this resource was a good one.
|
|
localCache[rc.GetName()] = cluster
|
|
if rc.GetName() == wi.target[0] {
|
|
returnCluster = cluster
|
|
}
|
|
}
|
|
|
|
// Update the cache in the v2Client only after we have confirmed that all
|
|
// resources in the received response were good.
|
|
for k, v := range localCache {
|
|
// TODO: Need to handle deletion of entries from the cache based on LDS
|
|
// watch calls. Not handling it does not affect correctness, but leads
|
|
// to unnecessary memory consumption.
|
|
v2c.rdsCache[k] = v
|
|
}
|
|
|
|
if returnCluster != "" {
|
|
// We stop the expiry timer and invoke the callback only when we have
|
|
// received the resource that we are watching for. Since RDS is an
|
|
// incremental protocol, the fact that we did not receive the resource
|
|
// that we are watching for in this response does not mean that the
|
|
// server does not know about it.
|
|
wi.expiryTimer.Stop()
|
|
wi.callback.(rdsCallback)(rdsUpdate{clusterName: returnCluster}, nil)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getClusterFromRouteConfiguration checks if the provided RouteConfiguration
|
|
// meets the expected criteria. If so, it returns a non-empty clusterName.
|
|
//
|
|
// A RouteConfiguration resource is considered valid when only if it contains a
|
|
// VirtualHost whose domain field matches the server name from the URI passed
|
|
// to the gRPC channel, and it contains a clusterName.
|
|
//
|
|
// The RouteConfiguration includes a list of VirtualHosts, which may have zero
|
|
// or more elements. We are interested in the element whose domains field
|
|
// matches the server name specified in the "xds:" URI (with port, if any,
|
|
// stripped off). The only field in the VirtualHost proto that the we are
|
|
// interested in is the list of routes. We only look at the last route in the
|
|
// list (the default route), whose match field must be empty and whose route
|
|
// field must be set. Inside that route message, the cluster field will
|
|
// contain the clusterName we are looking for.
|
|
func getClusterFromRouteConfiguration(rc *xdspb.RouteConfiguration, target string) string {
|
|
host, _, err := net.SplitHostPort(target)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, vh := range rc.GetVirtualHosts() {
|
|
for _, domain := range vh.GetDomains() {
|
|
// TODO: Add support for wildcard matching here?
|
|
if domain != host || len(vh.GetRoutes()) == 0 {
|
|
continue
|
|
}
|
|
dr := vh.Routes[len(vh.Routes)-1]
|
|
if dr.GetMatch() == nil && dr.GetRoute() != nil {
|
|
return dr.GetRoute().GetCluster()
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|