mirror of
https://github.com/coredns/coredns.git
synced 2025-11-03 02:33:21 -05:00
@@ -1,113 +1,181 @@
|
||||
/*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
This package contains code copied from github.com/grpc/grpc-co. The license for that code is:
|
||||
|
||||
// Package client implementation a full fledged gRPC client for the xDS API
|
||||
// used by the xds resolver and balancer implementations.
|
||||
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 xds implements a bidirectional stream to an envoy ADS management endpoint. It will stream
|
||||
// updates (CDS and EDS) from there to help load balance responses to DNS clients.
|
||||
package xds
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/coredns/coredns/plugin/traffic/xds/bootstrap"
|
||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||
|
||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||
adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Options provides all parameters required for the creation of an xDS client.
|
||||
type Options struct {
|
||||
// Config contains a fully populated bootstrap config. It is the
|
||||
// responsibility of the caller to use some sane defaults here if the
|
||||
// bootstrap process returned with certain fields left unspecified.
|
||||
Config bootstrap.Config
|
||||
// DialOpts contains dial options to be used when dialing the xDS server.
|
||||
DialOpts []grpc.DialOption
|
||||
}
|
||||
var log = clog.NewWithPlugin("traffic")
|
||||
|
||||
const (
|
||||
cdsURL = "type.googleapis.com/envoy.api.v2.Cluster"
|
||||
edsURL = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
|
||||
)
|
||||
|
||||
type adsStream adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient
|
||||
|
||||
// Client is a full fledged gRPC client which queries a set of discovery APIs
|
||||
// (collectively termed as xDS) on a remote management server, to discover
|
||||
// various dynamic resources. A single client object will be shared by the xds
|
||||
// resolver and balancer implementations.
|
||||
type Client struct {
|
||||
opts Options
|
||||
cc *grpc.ClientConn // Connection to the xDS server
|
||||
v2c *v2Client // Actual xDS client implementation using the v2 API
|
||||
|
||||
serviceCallback func(ServiceUpdate, error)
|
||||
cc *grpc.ClientConn
|
||||
ctx context.Context
|
||||
assignments assignment
|
||||
node *corepb.Node
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// New returns a new xdsClient configured with opts.
|
||||
func New(opts Options) (*Client, error) {
|
||||
switch {
|
||||
case opts.Config.BalancerName == "":
|
||||
return nil, errors.New("xds: no xds_server name provided in options")
|
||||
case opts.Config.Creds == nil:
|
||||
fmt.Printf("%s\n", errors.New("xds: no credentials provided in options"))
|
||||
case opts.Config.NodeProto == nil:
|
||||
return nil, errors.New("xds: no node_proto provided in options")
|
||||
}
|
||||
type assignment struct {
|
||||
mu sync.RWMutex
|
||||
cla map[string]*xdspb.ClusterLoadAssignment
|
||||
version int // not sure what do with and if we should discard all clusters.
|
||||
}
|
||||
|
||||
var dopts []grpc.DialOption
|
||||
if opts.Config.Creds == nil {
|
||||
dopts = append([]grpc.DialOption{grpc.WithInsecure()}, opts.DialOpts...)
|
||||
} else {
|
||||
dopts = append([]grpc.DialOption{opts.Config.Creds}, opts.DialOpts...)
|
||||
func (a assignment) SetClusterLoadAssignment(cluster string, cla *xdspb.ClusterLoadAssignment) {
|
||||
// if cla is nil we just found a cluster, check if we already know about it, or if we need to make
|
||||
// a new entry
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
_, ok := a.cla[cluster]
|
||||
if !ok {
|
||||
a.cla[cluster] = cla
|
||||
return
|
||||
}
|
||||
cc, err := grpc.Dial(opts.Config.BalancerName, dopts...)
|
||||
if cla == nil {
|
||||
return
|
||||
}
|
||||
a.cla[cluster] = cla
|
||||
|
||||
}
|
||||
|
||||
func (a assignment) ClusterLoadAssignment(cluster string) *xdspb.ClusterLoadAssignment {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a assignment) Clusters() []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
clusters := make([]string, len(a.cla))
|
||||
i := 0
|
||||
for k := range a.cla {
|
||||
clusters[i] = k
|
||||
i++
|
||||
}
|
||||
return clusters
|
||||
}
|
||||
|
||||
// New returns a new client that's dialed to addr using node as the local identifier.
|
||||
func New(addr, node string) (*Client, error) {
|
||||
// todo credentials
|
||||
opts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
cc, err := grpc.Dial(addr, opts...)
|
||||
if err != nil {
|
||||
// An error from a non-blocking dial indicates something serious.
|
||||
return nil, fmt.Errorf("xds: failed to dial balancer {%s}: %v", opts.Config.BalancerName, err)
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{cc: cc, node: &corepb.Node{Id: "test-id"}} // do more with this node data? Hostname port??
|
||||
c.assignments = assignment{cla: make(map[string]*xdspb.ClusterLoadAssignment)}
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
|
||||
println("dialed balancer at", opts.Config.BalancerName)
|
||||
|
||||
c := &Client{
|
||||
opts: opts,
|
||||
cc: cc,
|
||||
v2c: newV2Client(cc, opts.Config.NodeProto, func(int) time.Duration { return 0 }),
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close closes the gRPC connection to the xDS server.
|
||||
func (c *Client) Close() {
|
||||
// TODO: Should we invoke the registered callbacks here with an error that
|
||||
// the client is closed?
|
||||
c.v2c.close()
|
||||
c.cc.Close()
|
||||
func (c *Client) Close() { c.cancel(); c.cc.Close() }
|
||||
|
||||
func (c *Client) Run() (adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient, error) {
|
||||
cli := adsgrpc.NewAggregatedDiscoveryServiceClient(c.cc)
|
||||
stream, err := cli.StreamAggregatedResources(c.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (c *Client) Run() {
|
||||
c.v2c.run()
|
||||
func (c *Client) ClusterDiscovery(stream adsStream, version, nonce string, clusters []string) error {
|
||||
req := &xdspb.DiscoveryRequest{
|
||||
Node: c.node,
|
||||
TypeUrl: cdsURL,
|
||||
ResourceNames: clusters, // empty for all
|
||||
VersionInfo: version,
|
||||
ResponseNonce: nonce,
|
||||
}
|
||||
return stream.Send(req)
|
||||
}
|
||||
|
||||
// ServiceUpdate contains update about the service.
|
||||
type ServiceUpdate struct {
|
||||
Cluster string
|
||||
func (c *Client) EndpointDiscovery(stream adsStream, version, nonce string, clusters []string) error {
|
||||
req := &xdspb.DiscoveryRequest{
|
||||
Node: c.node,
|
||||
TypeUrl: edsURL,
|
||||
ResourceNames: clusters,
|
||||
VersionInfo: version,
|
||||
ResponseNonce: nonce,
|
||||
}
|
||||
return stream.Send(req)
|
||||
}
|
||||
|
||||
// WatchCluster uses CDS to discover information about the provided clusterName.
|
||||
func (c *Client) WatchCluster(clusterName string, cdsCb func(CDSUpdate, error)) (cancel func()) {
|
||||
return c.v2c.watchCDS(clusterName, cdsCb)
|
||||
}
|
||||
func (c *Client) Receive(stream adsStream) error {
|
||||
for {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// WatchEndpoints uses EDS to discover information about the endpoints in a cluster.
|
||||
func (c *Client) WatchEndpoints(clusterName string, edsCb func(*EDSUpdate, error)) (cancel func()) {
|
||||
return c.v2c.watchEDS(clusterName, edsCb)
|
||||
switch resp.GetTypeUrl() {
|
||||
case cdsURL:
|
||||
for _, r := range resp.GetResources() {
|
||||
var any ptypes.DynamicAny
|
||||
if err := ptypes.UnmarshalAny(r, &any); err != nil {
|
||||
continue
|
||||
}
|
||||
cluster, ok := any.Message.(*xdspb.Cluster)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
c.assignments.SetClusterLoadAssignment(cluster.GetName(), nil)
|
||||
}
|
||||
println("HERER", len(resp.GetResources()))
|
||||
log.Debug("Cluster discovery processed with %d resources", len(resp.GetResources()))
|
||||
// ack the CDS proto, with we we've got. (empty version would be NACK)
|
||||
if err := c.ClusterDiscovery(stream, resp.GetVersionInfo(), resp.GetNonce(), c.assignments.Clusters()); err != nil {
|
||||
log.Warningf("Failed to acknowledge cluster discovery: %s", err)
|
||||
}
|
||||
// need to figure out how to handle the version exactly.
|
||||
|
||||
// now kick off discovery for endpoints
|
||||
if err := c.EndpointDiscovery(stream, "", "", c.assignments.Clusters()); err != nil {
|
||||
log.Warningf("Failed to perform endpoint discovery: %s", err)
|
||||
}
|
||||
|
||||
case edsURL:
|
||||
println("EDS")
|
||||
default:
|
||||
log.Warningf("Unknown response URL for discovery: %q", resp.GetTypeUrl())
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user