mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 16:24:19 -04:00
@@ -47,11 +47,7 @@ is smart enough to select the best one. When SRV records are returned, the endpo
|
|||||||
are synthesized `endpoint-<N>.<cluster>.<zone>` that carries the IP address. Querying for these
|
are synthesized `endpoint-<N>.<cluster>.<zone>` that carries the IP address. Querying for these
|
||||||
synthesized names works as well.
|
synthesized names works as well.
|
||||||
|
|
||||||
[gRPC LB SRV records](https://github.com/grpc/proposal/blob/master/A5-grpclb-in-dns.md) are
|
*Traffic* implements version 2 of the xDS API. It works with the management server as written in
|
||||||
supported and returned by the *traffic* plugin for all clusters. The returned endpoints are,
|
|
||||||
however, the ones from the management cluster.
|
|
||||||
|
|
||||||
*Traffic* implements version 3 of the xDS API. It works with the management server as written in
|
|
||||||
<https://github.com/miekg/xds>.
|
<https://github.com/miekg/xds>.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package traffic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// See https://github.com/grpc/grpc/blob/master/doc/service_config.md for the fields in this proto.
|
|
||||||
// We encode it as json and return it in a TXT field.
|
|
||||||
// TOOD(miek): balancer name should not be hardcoded
|
|
||||||
var lbTXT = `grpc_config=[{"serviceConfig":{"loadBalancingConfig":[{"eds_experimental":{"Cluster": "xds", "EDSServiceName":"%s", "BalancerName":"xds"}}]}}]`
|
|
||||||
|
|
||||||
func txt(z, cluster string) []dns.RR {
|
|
||||||
return []dns.RR{&dns.TXT{
|
|
||||||
Hdr: dns.RR_Header{Name: z, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 5},
|
|
||||||
Txt: []string{fmt.Sprintf(lbTXT, cluster)},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package traffic
|
package traffic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy"
|
"github.com/caddyserver/caddy"
|
||||||
@@ -14,17 +13,6 @@ func TestSetup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLBTxt(t *testing.T) {
|
|
||||||
for _, txt := range []string{lbTXT} {
|
|
||||||
if _, err := json.Marshal(txt); err != nil {
|
|
||||||
t.Errorf("Failed to marshal grpc serverConfig: %s", err)
|
|
||||||
}
|
|
||||||
if len(txt) > 255 {
|
|
||||||
t.Errorf("Too long grpc serverConfig (>255): %d", len(txt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseTraffic(t *testing.T) {
|
func TestParseTraffic(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
|
|||||||
@@ -54,28 +54,10 @@ func (t *Traffic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
|||||||
// ok this cluster doesn't exist, potentially due to extra labels, which may be garbage or legit queries:
|
// ok this cluster doesn't exist, potentially due to extra labels, which may be garbage or legit queries:
|
||||||
// legit is:
|
// legit is:
|
||||||
// endpoint-N.cluster
|
// endpoint-N.cluster
|
||||||
// _grpclb._tcp.cluster
|
|
||||||
// _tcp.cluster
|
// _tcp.cluster
|
||||||
// _grpc_config.cluster (singled out here, but not handled)
|
|
||||||
labels := dns.SplitDomainName(cluster)
|
labels := dns.SplitDomainName(cluster)
|
||||||
switch len(labels) {
|
switch len(labels) {
|
||||||
case 2:
|
case 2:
|
||||||
// endpoint or _tcp or _grpc_config query
|
|
||||||
if strings.ToLower(labels[0]) == "_tcp" {
|
|
||||||
// nodata, because empty non-terminal
|
|
||||||
m.Ns = soa(state.Zone)
|
|
||||||
m.Rcode = dns.RcodeSuccess
|
|
||||||
w.WriteMsg(m)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(strings.ToLower(labels[0]), "_grpc_config") {
|
|
||||||
// this is the grpc config blob encoded in a TXT record, see documentation for lbTXT.
|
|
||||||
m.Answer = txt(state.Zone, labels[1]) // 1 is the cluster
|
|
||||||
m.Rcode = dns.RcodeSuccess
|
|
||||||
w.WriteMsg(m)
|
|
||||||
return 0, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(strings.ToLower(labels[0]), "endpoint-") {
|
if strings.HasPrefix(strings.ToLower(labels[0]), "endpoint-") {
|
||||||
// recheck if the cluster exist.
|
// recheck if the cluster exist.
|
||||||
cluster = labels[1]
|
cluster = labels[1]
|
||||||
@@ -88,24 +70,6 @@ func (t *Traffic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
|||||||
}
|
}
|
||||||
return t.serveEndpoint(ctx, state, labels[0], cluster, healthy)
|
return t.serveEndpoint(ctx, state, labels[0], cluster, healthy)
|
||||||
}
|
}
|
||||||
case 3:
|
|
||||||
if strings.ToLower(labels[0]) != "_grpclb" || strings.ToLower(labels[1]) != "_tcp" {
|
|
||||||
m.Ns = soa(state.Zone)
|
|
||||||
m.Rcode = dns.RcodeNameError
|
|
||||||
w.WriteMsg(m)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
// OK, _grcplb._tcp query; we need to return the endpoint for the cluster in this query
|
|
||||||
cluster = labels[2]
|
|
||||||
sockaddr, ok = t.c.Select(cluster, healthy)
|
|
||||||
if !ok {
|
|
||||||
// nodata error when this cluster doesn't exist.
|
|
||||||
m.Ns = soa(state.Zone)
|
|
||||||
m.Rcode = dns.RcodeSuccess
|
|
||||||
w.WriteMsg(m)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
m.Ns = soa(state.Zone)
|
m.Ns = soa(state.Zone)
|
||||||
m.Rcode = dns.RcodeNameError
|
m.Rcode = dns.RcodeNameError
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import (
|
|||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
"github.com/coredns/coredns/plugin/traffic/xds"
|
"github.com/coredns/coredns/plugin/traffic/xds"
|
||||||
|
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
xdspb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
corepb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
|
endpointpb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
@@ -23,7 +24,7 @@ func TestTraffic(t *testing.T) {
|
|||||||
tr := &Traffic{c: c, origins: []string{"lb.example.org."}}
|
tr := &Traffic{c: c, origins: []string{"lb.example.org."}}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
cla *endpointpb.ClusterLoadAssignment
|
cla *xdspb2.ClusterLoadAssignment
|
||||||
cluster string
|
cluster string
|
||||||
qtype uint16
|
qtype uint16
|
||||||
rcode int
|
rcode int
|
||||||
@@ -31,98 +32,98 @@ func TestTraffic(t *testing.T) {
|
|||||||
ns bool // should there be a ns section.
|
ns bool // should there be a ns section.
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{},
|
cla: &xdspb2.ClusterLoadAssignment{},
|
||||||
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true,
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{},
|
cla: &xdspb2.ClusterLoadAssignment{},
|
||||||
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, ns: true,
|
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, ns: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{},
|
cla: &xdspb2.ClusterLoadAssignment{},
|
||||||
cluster: "does-not-exist", qtype: dns.TypeA, rcode: dns.RcodeNameError, ns: true,
|
cluster: "does-not-exist", qtype: dns.TypeA, rcode: dns.RcodeNameError, ns: true,
|
||||||
},
|
},
|
||||||
// healthy endpoint
|
// healthy endpoint
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", 18008, corepb.HealthStatus_HEALTHY}}),
|
Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", 18008, corepb2.HealthStatus_HEALTHY}}),
|
||||||
},
|
},
|
||||||
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.1",
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{{"::1", 18008, corepb.HealthStatus_HEALTHY}}),
|
Endpoints: endpoints([]EndpointHealth{{"::1", 18008, corepb2.HealthStatus_HEALTHY}}),
|
||||||
},
|
},
|
||||||
cluster: "web", qtype: dns.TypeAAAA, rcode: dns.RcodeSuccess, answer: "::1",
|
cluster: "web", qtype: dns.TypeAAAA, rcode: dns.RcodeSuccess, answer: "::1",
|
||||||
},
|
},
|
||||||
// unknown endpoint
|
// unknown endpoint
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", 18008, corepb.HealthStatus_UNKNOWN}}),
|
Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", 18008, corepb2.HealthStatus_UNKNOWN}}),
|
||||||
},
|
},
|
||||||
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true,
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true,
|
||||||
},
|
},
|
||||||
// unknown endpoint and healthy endpoint
|
// unknown endpoint and healthy endpoint
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{
|
Endpoints: endpoints([]EndpointHealth{
|
||||||
{"127.0.0.1", 18008, corepb.HealthStatus_UNKNOWN},
|
{"127.0.0.1", 18008, corepb2.HealthStatus_UNKNOWN},
|
||||||
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.2", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.2",
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.2",
|
||||||
},
|
},
|
||||||
// unknown endpoint and healthy endpoint, TXT query
|
// unknown endpoint and healthy endpoint, TXT query
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{
|
Endpoints: endpoints([]EndpointHealth{
|
||||||
{"127.0.0.1", 18008, corepb.HealthStatus_UNKNOWN},
|
{"127.0.0.1", 18008, corepb2.HealthStatus_UNKNOWN},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cluster: "web", qtype: dns.TypeTXT, rcode: dns.RcodeSuccess, answer: "endpoint-0.web.lb.example.org.",
|
cluster: "web", qtype: dns.TypeTXT, rcode: dns.RcodeSuccess, answer: "endpoint-0.web.lb.example.org.",
|
||||||
},
|
},
|
||||||
// SRV query healthy endpoint
|
// SRV query healthy endpoint
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{
|
Endpoints: endpoints([]EndpointHealth{
|
||||||
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.2", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, answer: "endpoint-0.web.lb.example.org.",
|
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, answer: "endpoint-0.web.lb.example.org.",
|
||||||
},
|
},
|
||||||
// A query for endpoint-0.
|
// A query for endpoint-0.
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{
|
Endpoints: endpoints([]EndpointHealth{
|
||||||
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.2", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cluster: "endpoint-0.web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.2",
|
cluster: "endpoint-0.web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.2",
|
||||||
},
|
},
|
||||||
// A query for endpoint-1.
|
// A query for endpoint-1.
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{
|
Endpoints: endpoints([]EndpointHealth{
|
||||||
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.2", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
{"127.0.0.3", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.3", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cluster: "endpoint-1.web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.3",
|
cluster: "endpoint-1.web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.3",
|
||||||
},
|
},
|
||||||
// TXT query for _grpc_config
|
// TXT query for _grpc_config
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{
|
Endpoints: endpoints([]EndpointHealth{
|
||||||
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.2", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cluster: "_grpc_config.web", qtype: dns.TypeTXT, rcode: dns.RcodeSuccess,
|
cluster: "_grpc_config.web", qtype: dns.TypeTXT, rcode: dns.RcodeSuccess,
|
||||||
@@ -182,7 +183,7 @@ func TestTrafficSRV(t *testing.T) {
|
|||||||
tr := &Traffic{c: c, origins: []string{"lb.example.org."}}
|
tr := &Traffic{c: c, origins: []string{"lb.example.org."}}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
cla *endpointpb.ClusterLoadAssignment
|
cla *xdspb2.ClusterLoadAssignment
|
||||||
cluster string
|
cluster string
|
||||||
qtype uint16
|
qtype uint16
|
||||||
rcode int
|
rcode int
|
||||||
@@ -190,11 +191,11 @@ func TestTrafficSRV(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// SRV query healthy endpoint
|
// SRV query healthy endpoint
|
||||||
{
|
{
|
||||||
cla: &endpointpb.ClusterLoadAssignment{
|
cla: &xdspb2.ClusterLoadAssignment{
|
||||||
ClusterName: "web",
|
ClusterName: "web",
|
||||||
Endpoints: endpoints([]EndpointHealth{
|
Endpoints: endpoints([]EndpointHealth{
|
||||||
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.2", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
{"127.0.0.3", 18008, corepb.HealthStatus_HEALTHY},
|
{"127.0.0.3", 18008, corepb2.HealthStatus_HEALTHY},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, answer: 2,
|
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, answer: 2,
|
||||||
@@ -226,76 +227,33 @@ func TestTrafficSRV(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrafficManagement(t *testing.T) {
|
|
||||||
c, err := xds.New("127.0.0.1:0", "test-id", grpc.WithInsecure())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tr := &Traffic{c: c, origins: []string{"lb.example.org."}, mgmt: "xds"}
|
|
||||||
|
|
||||||
for _, cla := range []*endpointpb.ClusterLoadAssignment{
|
|
||||||
&endpointpb.ClusterLoadAssignment{
|
|
||||||
ClusterName: "web",
|
|
||||||
Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", 18008, corepb.HealthStatus_HEALTHY}}),
|
|
||||||
},
|
|
||||||
&endpointpb.ClusterLoadAssignment{
|
|
||||||
ClusterName: "xds",
|
|
||||||
Endpoints: endpoints([]EndpointHealth{{"::1", 18008, corepb.HealthStatus_HEALTHY}}),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
a := xds.NewAssignment()
|
|
||||||
a.SetClusterLoadAssignment(cla.ClusterName, cla)
|
|
||||||
c.SetAssignments(a)
|
|
||||||
}
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
// Now we ask for the grpclb endpoint in the web cluster, this should give us the endpoint of the xds (mgmt) cluster.
|
|
||||||
// ; ANSWER SECTION:
|
|
||||||
// _grpclb._tcp.web.lb.example.org. 5 IN SRV 100 100 18008 endpoint-0.xds.lb.example.org.
|
|
||||||
// ;; ADDITIONAL SECTION:
|
|
||||||
// endpoint-0.xds.lb.example.org. 5 IN AAAA ::1
|
|
||||||
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetQuestion("_grpclb._tcp.web.lb.example.org.", dns.TypeSRV)
|
|
||||||
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
||||||
if _, err := tr.ServeDNS(ctx, rec, m); err != nil {
|
|
||||||
t.Errorf("Expected no error, but got %q", err)
|
|
||||||
}
|
|
||||||
if len(rec.Msg.Answer) == 0 {
|
|
||||||
t.Fatalf("Expected answer section, got none")
|
|
||||||
}
|
|
||||||
if x := rec.Msg.Answer[0].(*dns.SRV).Target; x != "endpoint-0.xds.lb.example.org." {
|
|
||||||
t.Errorf("Expected %s, got %s", "endpoint-0.xds.lb.example.org.", x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type EndpointHealth struct {
|
type EndpointHealth struct {
|
||||||
Address string
|
Address string
|
||||||
Port uint16
|
Port uint16
|
||||||
Health corepb.HealthStatus
|
Health corepb2.HealthStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func endpoints(e []EndpointHealth) []*endpointpb.LocalityLbEndpoints {
|
func endpoints(e []EndpointHealth) []*endpointpb2.LocalityLbEndpoints {
|
||||||
return endpointsWithLocality(e, xds.Locality{})
|
return endpointsWithLocality(e, xds.Locality{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func endpointsWithLocality(e []EndpointHealth, loc xds.Locality) []*endpointpb.LocalityLbEndpoints {
|
func endpointsWithLocality(e []EndpointHealth, loc xds.Locality) []*endpointpb2.LocalityLbEndpoints {
|
||||||
ep := make([]*endpointpb.LocalityLbEndpoints, len(e))
|
ep := make([]*endpointpb2.LocalityLbEndpoints, len(e))
|
||||||
for i := range e {
|
for i := range e {
|
||||||
ep[i] = &endpointpb.LocalityLbEndpoints{
|
ep[i] = &endpointpb2.LocalityLbEndpoints{
|
||||||
Locality: &corepb.Locality{
|
Locality: &corepb2.Locality{
|
||||||
Region: loc.Region,
|
Region: loc.Region,
|
||||||
Zone: loc.Zone,
|
Zone: loc.Zone,
|
||||||
SubZone: loc.SubZone,
|
SubZone: loc.SubZone,
|
||||||
},
|
},
|
||||||
LbEndpoints: []*endpointpb.LbEndpoint{{
|
LbEndpoints: []*endpointpb2.LbEndpoint{{
|
||||||
HostIdentifier: &endpointpb.LbEndpoint_Endpoint{
|
HostIdentifier: &endpointpb2.LbEndpoint_Endpoint{
|
||||||
Endpoint: &endpointpb.Endpoint{
|
Endpoint: &endpointpb2.Endpoint{
|
||||||
Address: &corepb.Address{
|
Address: &corepb2.Address{
|
||||||
Address: &corepb.Address_SocketAddress{
|
Address: &corepb2.Address_SocketAddress{
|
||||||
SocketAddress: &corepb.SocketAddress{
|
SocketAddress: &corepb2.SocketAddress{
|
||||||
Address: e[i].Address,
|
Address: e[i].Address,
|
||||||
PortSpecifier: &corepb.SocketAddress_PortValue{
|
PortSpecifier: &corepb2.SocketAddress_PortValue{
|
||||||
PortValue: uint32(e[i].Port),
|
PortValue: uint32(e[i].Port),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
xdspb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
corepb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SocketAddress holds a corepb.SocketAddress and a health status
|
// SocketAddress holds a corepb2.SocketAddress and a health status
|
||||||
type SocketAddress struct {
|
type SocketAddress struct {
|
||||||
*corepb.SocketAddress
|
*corepb2.SocketAddress
|
||||||
Health corepb.HealthStatus
|
Health corepb2.HealthStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns the address from s.
|
// Address returns the address from s.
|
||||||
@@ -23,16 +23,16 @@ func (s *SocketAddress) Port() uint16 { return uint16(s.GetPortValue()) }
|
|||||||
|
|
||||||
type assignment struct {
|
type assignment struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
cla map[string]*endpointpb.ClusterLoadAssignment
|
cla map[string]*xdspb2.ClusterLoadAssignment
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAssignment returns a pointer to an assignment.
|
// NewAssignment returns a pointer to an assignment.
|
||||||
func NewAssignment() *assignment {
|
func NewAssignment() *assignment {
|
||||||
return &assignment{cla: make(map[string]*endpointpb.ClusterLoadAssignment)}
|
return &assignment{cla: make(map[string]*xdspb2.ClusterLoadAssignment)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetClusterLoadAssignment sets the assignment for the cluster to cla.
|
// SetClusterLoadAssignment sets the assignment for the cluster to cla.
|
||||||
func (a *assignment) SetClusterLoadAssignment(cluster string, cla *endpointpb.ClusterLoadAssignment) {
|
func (a *assignment) SetClusterLoadAssignment(cluster string, cla *xdspb2.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.
|
// 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()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
@@ -49,7 +49,7 @@ func (a *assignment) SetClusterLoadAssignment(cluster string, cla *endpointpb.Cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ClusterLoadAssignment returns the assignment for the cluster or nil if there is none.
|
// ClusterLoadAssignment returns the assignment for the cluster or nil if there is none.
|
||||||
func (a *assignment) ClusterLoadAssignment(cluster string) *endpointpb.ClusterLoadAssignment {
|
func (a *assignment) ClusterLoadAssignment(cluster string) *xdspb2.ClusterLoadAssignment {
|
||||||
a.mu.RLock()
|
a.mu.RLock()
|
||||||
cla, ok := a.cla[cluster]
|
cla, ok := a.cla[cluster]
|
||||||
a.mu.RUnlock()
|
a.mu.RUnlock()
|
||||||
@@ -82,7 +82,7 @@ func (a *assignment) Select(cluster string, healthy bool) (*SocketAddress, bool)
|
|||||||
health := 0
|
health := 0
|
||||||
for _, ep := range cla.Endpoints {
|
for _, ep := range cla.Endpoints {
|
||||||
for _, lb := range ep.GetLbEndpoints() {
|
for _, lb := range ep.GetLbEndpoints() {
|
||||||
if healthy && lb.GetHealthStatus() != corepb.HealthStatus_HEALTHY {
|
if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
weight += int(lb.GetLoadBalancingWeight().GetValue())
|
weight += int(lb.GetLoadBalancingWeight().GetValue())
|
||||||
@@ -99,7 +99,7 @@ func (a *assignment) Select(cluster string, healthy bool) (*SocketAddress, bool)
|
|||||||
i := 0
|
i := 0
|
||||||
for _, ep := range cla.Endpoints {
|
for _, ep := range cla.Endpoints {
|
||||||
for _, lb := range ep.GetLbEndpoints() {
|
for _, lb := range ep.GetLbEndpoints() {
|
||||||
if healthy && lb.GetHealthStatus() != corepb.HealthStatus_HEALTHY {
|
if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r == i {
|
if r == i {
|
||||||
@@ -114,7 +114,7 @@ func (a *assignment) Select(cluster string, healthy bool) (*SocketAddress, bool)
|
|||||||
r := rand.Intn(health) + 1
|
r := rand.Intn(health) + 1
|
||||||
for _, ep := range cla.Endpoints {
|
for _, ep := range cla.Endpoints {
|
||||||
for _, lb := range ep.GetLbEndpoints() {
|
for _, lb := range ep.GetLbEndpoints() {
|
||||||
if healthy && lb.GetHealthStatus() != corepb.HealthStatus_HEALTHY {
|
if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r -= int(lb.GetLoadBalancingWeight().GetValue())
|
r -= int(lb.GetLoadBalancingWeight().GetValue())
|
||||||
@@ -136,7 +136,7 @@ func (a *assignment) All(cluster string, healthy bool) ([]*SocketAddress, bool)
|
|||||||
sa := []*SocketAddress{}
|
sa := []*SocketAddress{}
|
||||||
for _, ep := range cla.Endpoints {
|
for _, ep := range cla.Endpoints {
|
||||||
for _, lb := range ep.GetLbEndpoints() {
|
for _, lb := range ep.GetLbEndpoints() {
|
||||||
if healthy && lb.GetHealthStatus() != corepb.HealthStatus_HEALTHY {
|
if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sa = append(sa, &SocketAddress{lb.GetEndpoint().GetAddress().GetSocketAddress(), lb.GetHealthStatus()})
|
sa = append(sa, &SocketAddress{lb.GetEndpoint().GetAddress().GetSocketAddress(), lb.GetHealthStatus()})
|
||||||
|
|||||||
@@ -22,16 +22,14 @@ package xds
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/coredns/coredns/coremain"
|
"github.com/coredns/coredns/coremain"
|
||||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
|
|
||||||
clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
xdspb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
corepb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
adspb2 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
|
||||||
"github.com/golang/protobuf/ptypes"
|
"github.com/golang/protobuf/ptypes"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
@@ -39,17 +37,17 @@ import (
|
|||||||
var log = clog.NewWithPlugin("traffic: xds")
|
var log = clog.NewWithPlugin("traffic: xds")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cdsURL = "type.googleapis.com/envoy.config.cluster.v3.Cluster"
|
clusterType = "type.googleapis.com/envoy.api.v2.Cluster"
|
||||||
edsURL = "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
|
endpointType = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
type adsStream xdspb.AggregatedDiscoveryService_StreamAggregatedResourcesClient
|
type adsStream adspb2.AggregatedDiscoveryService_StreamAggregatedResourcesClient
|
||||||
|
|
||||||
// Client talks to the grpc manager's endpoint to get load assignments.
|
// Client talks to the grpc manager's endpoint to get load assignments.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
cc *grpc.ClientConn
|
cc *grpc.ClientConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
node *corepb.Node
|
node *corepb2.Node
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
to string // upstream hosts, mostly here for logging purposes
|
to string // upstream hosts, mostly here for logging purposes
|
||||||
@@ -66,8 +64,8 @@ func New(addr, node string, opts ...grpc.DialOption) (*Client, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c := &Client{cc: cc, to: addr, node: &corepb.Node{Id: node, UserAgentName: "CoreDNS", UserAgentVersionType: &corepb.Node_UserAgentVersion{UserAgentVersion: coremain.CoreVersion}}}
|
c := &Client{cc: cc, to: addr, node: &corepb2.Node{Id: node, UserAgentName: "CoreDNS", UserAgentVersionType: &corepb2.Node_UserAgentVersion{UserAgentVersion: coremain.CoreVersion}}}
|
||||||
c.assignments = &assignment{cla: make(map[string]*endpointpb.ClusterLoadAssignment)}
|
c.assignments = &assignment{cla: make(map[string]*xdspb2.ClusterLoadAssignment)}
|
||||||
c.version, c.nonce = make(map[string]string), make(map[string]string)
|
c.version, c.nonce = make(map[string]string), make(map[string]string)
|
||||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
@@ -87,7 +85,7 @@ func (c *Client) Run() error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := xdspb.NewAggregatedDiscoveryServiceClient(c.cc)
|
cli := adspb2.NewAggregatedDiscoveryServiceClient(c.cc)
|
||||||
stream, err := cli.StreamAggregatedResources(c.ctx)
|
stream, err := cli.StreamAggregatedResources(c.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -95,7 +93,7 @@ func (c *Client) Run() error {
|
|||||||
|
|
||||||
if first {
|
if first {
|
||||||
// send first request, to create stream, then wait for ADS to send us updates.
|
// send first request, to create stream, then wait for ADS to send us updates.
|
||||||
if err := c.clusterDiscovery(stream, c.Version(cdsURL), c.Nonce(cdsURL), []string{}); err != nil {
|
if err := c.clusterDiscovery(stream, c.Version(clusterType), c.Nonce(clusterType), []string{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Infof("gRPC stream established to %q", c.to) // might fail??
|
log.Infof("gRPC stream established to %q", c.to) // might fail??
|
||||||
@@ -111,9 +109,9 @@ func (c *Client) Run() error {
|
|||||||
|
|
||||||
// clusterDiscovery sends a cluster DiscoveryRequest on the stream.
|
// clusterDiscovery sends a cluster DiscoveryRequest on the stream.
|
||||||
func (c *Client) clusterDiscovery(stream adsStream, version, nonce string, clusters []string) error {
|
func (c *Client) clusterDiscovery(stream adsStream, version, nonce string, clusters []string) error {
|
||||||
req := &xdspb.DiscoveryRequest{
|
req := &xdspb2.DiscoveryRequest{
|
||||||
Node: c.node,
|
Node: c.node,
|
||||||
TypeUrl: cdsURL,
|
TypeUrl: clusterType,
|
||||||
ResourceNames: clusters, // empty for all
|
ResourceNames: clusters, // empty for all
|
||||||
VersionInfo: version,
|
VersionInfo: version,
|
||||||
ResponseNonce: nonce,
|
ResponseNonce: nonce,
|
||||||
@@ -123,9 +121,9 @@ func (c *Client) clusterDiscovery(stream adsStream, version, nonce string, clust
|
|||||||
|
|
||||||
// endpointDiscovery sends a endpoint DiscoveryRequest on the stream.
|
// endpointDiscovery sends a endpoint DiscoveryRequest on the stream.
|
||||||
func (c *Client) endpointDiscovery(stream adsStream, version, nonce string, clusters []string) error {
|
func (c *Client) endpointDiscovery(stream adsStream, version, nonce string, clusters []string) error {
|
||||||
req := &xdspb.DiscoveryRequest{
|
req := &xdspb2.DiscoveryRequest{
|
||||||
Node: c.node,
|
Node: c.node,
|
||||||
TypeUrl: edsURL,
|
TypeUrl: endpointType,
|
||||||
ResourceNames: clusters,
|
ResourceNames: clusters,
|
||||||
VersionInfo: version,
|
VersionInfo: version,
|
||||||
ResponseNonce: nonce,
|
ResponseNonce: nonce,
|
||||||
@@ -142,7 +140,7 @@ func (c *Client) receive(stream adsStream) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch resp.GetTypeUrl() {
|
switch resp.GetTypeUrl() {
|
||||||
case cdsURL:
|
case clusterType:
|
||||||
a := NewAssignment()
|
a := NewAssignment()
|
||||||
for _, r := range resp.GetResources() {
|
for _, r := range resp.GetResources() {
|
||||||
var any ptypes.DynamicAny
|
var any ptypes.DynamicAny
|
||||||
@@ -150,33 +148,33 @@ func (c *Client) receive(stream adsStream) error {
|
|||||||
log.Debugf("Failed to unmarshal cluster discovery: %s", err)
|
log.Debugf("Failed to unmarshal cluster discovery: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cluster, ok := any.Message.(*clusterpb.Cluster)
|
cluster, ok := any.Message.(*xdspb2.Cluster)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
a.SetClusterLoadAssignment(cluster.GetName(), nil)
|
a.SetClusterLoadAssignment(cluster.GetName(), nil)
|
||||||
}
|
}
|
||||||
// set our local administration and ack the reply. Empty version would signal NACK.
|
// set our local administration and ack the reply. Empty version would signal NACK.
|
||||||
c.SetNonce(cdsURL, resp.GetNonce())
|
c.SetNonce(clusterType, resp.GetNonce())
|
||||||
c.SetVersion(cdsURL, resp.GetVersionInfo())
|
c.SetVersion(clusterType, resp.GetVersionInfo())
|
||||||
c.SetAssignments(a)
|
c.SetAssignments(a)
|
||||||
c.clusterDiscovery(stream, resp.GetVersionInfo(), resp.GetNonce(), a.clusters())
|
c.clusterDiscovery(stream, resp.GetVersionInfo(), resp.GetNonce(), a.clusters())
|
||||||
|
|
||||||
log.Debugf("Cluster discovery processed with %d resources, version %q and nonce %q", len(resp.GetResources()), c.Version(cdsURL), c.Nonce(cdsURL))
|
log.Debugf("Cluster discovery processed with %d resources, version %q and nonce %q", len(resp.GetResources()), c.Version(clusterType), c.Nonce(clusterType))
|
||||||
ClusterGauge.Set(float64(len(resp.GetResources())))
|
ClusterGauge.Set(float64(len(resp.GetResources())))
|
||||||
|
|
||||||
// now kick off discovery for endpoints
|
// now kick off discovery for endpoints
|
||||||
if err := c.endpointDiscovery(stream, c.Version(edsURL), c.Nonce(edsURL), a.clusters()); err != nil {
|
if err := c.endpointDiscovery(stream, c.Version(endpointType), c.Nonce(endpointType), a.clusters()); err != nil {
|
||||||
log.Debug(err)
|
log.Debug(err)
|
||||||
}
|
}
|
||||||
case edsURL:
|
case endpointType:
|
||||||
for _, r := range resp.GetResources() {
|
for _, r := range resp.GetResources() {
|
||||||
var any ptypes.DynamicAny
|
var any ptypes.DynamicAny
|
||||||
if err := ptypes.UnmarshalAny(r, &any); err != nil {
|
if err := ptypes.UnmarshalAny(r, &any); err != nil {
|
||||||
log.Debugf("Failed to unmarshal endpoint discovery: %s", err)
|
log.Debugf("Failed to unmarshal endpoint discovery: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cla, ok := any.Message.(*endpointpb.ClusterLoadAssignment)
|
cla, ok := any.Message.(*xdspb2.ClusterLoadAssignment)
|
||||||
if !ok {
|
if !ok {
|
||||||
// TODO warn/err here?
|
// TODO warn/err here?
|
||||||
continue
|
continue
|
||||||
@@ -185,14 +183,14 @@ func (c *Client) receive(stream adsStream) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
// set our local administration and ack the reply. Empty version would signal NACK.
|
// set our local administration and ack the reply. Empty version would signal NACK.
|
||||||
c.SetNonce(edsURL, resp.GetNonce())
|
c.SetNonce(endpointType, resp.GetNonce())
|
||||||
c.SetVersion(edsURL, resp.GetVersionInfo())
|
c.SetVersion(endpointType, resp.GetVersionInfo())
|
||||||
|
|
||||||
log.Debugf("Endpoint discovery processed with %d resources, version %q and nonce %q", len(resp.GetResources()), c.Version(edsURL), c.Nonce(edsURL))
|
log.Debugf("Endpoint discovery processed with %d resources, version %q and nonce %q", len(resp.GetResources()), c.Version(endpointType), c.Nonce(endpointType))
|
||||||
EndpointGauge.Set(float64(len(resp.GetResources())))
|
EndpointGauge.Set(float64(len(resp.GetResources())))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown response URL for discovery: %q", resp.GetTypeUrl())
|
// ignore anything we don't know how to process. Probably should NACK these properly.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user