2019-10-05 11:45:45 +01:00
|
|
|
package traffic
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
|
|
|
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
|
|
|
|
"github.com/coredns/coredns/plugin/test"
|
|
|
|
|
"github.com/coredns/coredns/plugin/traffic/xds"
|
|
|
|
|
|
2020-01-31 07:36:35 +01:00
|
|
|
corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
|
|
|
|
endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
2019-10-05 11:45:45 +01:00
|
|
|
"github.com/miekg/dns"
|
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestTraffic(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."}}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
2020-01-31 07:36:35 +01:00
|
|
|
cla *endpointpb.ClusterLoadAssignment
|
2019-10-05 11:45:45 +01:00
|
|
|
cluster string
|
|
|
|
|
qtype uint16
|
|
|
|
|
rcode int
|
|
|
|
|
answer string // address value of the A/AAAA record.
|
|
|
|
|
ns bool // should there be a ns section.
|
|
|
|
|
}{
|
|
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{},
|
2019-10-05 11:45:45 +01:00
|
|
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{},
|
2019-10-05 11:45:45 +01:00
|
|
|
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, ns: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{},
|
2020-01-17 16:51:19 +01:00
|
|
|
cluster: "does-not-exist", qtype: dns.TypeA, rcode: dns.RcodeNameError, ns: true,
|
|
|
|
|
},
|
2020-01-19 08:30:13 +01:00
|
|
|
// healthy endpoint
|
2019-10-05 11:45:45 +01:00
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2019-10-05 11:45:45 +01:00
|
|
|
ClusterName: "web",
|
2020-01-18 20:12:25 +01:00
|
|
|
Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", 18008, corepb.HealthStatus_HEALTHY}}),
|
2019-10-05 11:45:45 +01:00
|
|
|
},
|
|
|
|
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.1",
|
|
|
|
|
},
|
2020-01-18 20:12:25 +01:00
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2020-01-18 20:12:25 +01:00
|
|
|
ClusterName: "web",
|
|
|
|
|
Endpoints: endpoints([]EndpointHealth{{"::1", 18008, corepb.HealthStatus_HEALTHY}}),
|
|
|
|
|
},
|
|
|
|
|
cluster: "web", qtype: dns.TypeAAAA, rcode: dns.RcodeSuccess, answer: "::1",
|
|
|
|
|
},
|
2020-01-19 08:30:13 +01:00
|
|
|
// unknown endpoint
|
2019-10-05 11:45:45 +01:00
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2019-10-05 11:45:45 +01:00
|
|
|
ClusterName: "web",
|
2020-01-18 20:12:25 +01:00
|
|
|
Endpoints: endpoints([]EndpointHealth{{"127.0.0.1", 18008, corepb.HealthStatus_UNKNOWN}}),
|
2019-10-05 11:45:45 +01:00
|
|
|
},
|
|
|
|
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, ns: true,
|
|
|
|
|
},
|
2020-01-19 08:30:13 +01:00
|
|
|
// unknown endpoint and healthy endpoint
|
2020-01-17 16:51:19 +01:00
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2020-01-17 16:51:19 +01:00
|
|
|
ClusterName: "web",
|
|
|
|
|
Endpoints: endpoints([]EndpointHealth{
|
2020-01-18 20:12:25 +01:00
|
|
|
{"127.0.0.1", 18008, corepb.HealthStatus_UNKNOWN},
|
|
|
|
|
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
2020-01-17 16:51:19 +01:00
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
cluster: "web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.2",
|
|
|
|
|
},
|
2020-01-19 08:30:13 +01:00
|
|
|
// SRV query healthy endpoint
|
2020-01-18 20:12:25 +01:00
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2020-01-18 20:12:25 +01:00
|
|
|
ClusterName: "web",
|
|
|
|
|
Endpoints: endpoints([]EndpointHealth{
|
|
|
|
|
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, answer: "endpoint-0.web.lb.example.org.",
|
|
|
|
|
},
|
|
|
|
|
// A query for endpoint-0.
|
|
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2020-01-18 20:12:25 +01:00
|
|
|
ClusterName: "web",
|
|
|
|
|
Endpoints: endpoints([]EndpointHealth{
|
|
|
|
|
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
cluster: "endpoint-0.web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.2",
|
|
|
|
|
},
|
2020-01-19 08:30:13 +01:00
|
|
|
// A query for endpoint-1.
|
|
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2020-01-19 08:30:13 +01:00
|
|
|
ClusterName: "web",
|
|
|
|
|
Endpoints: endpoints([]EndpointHealth{
|
|
|
|
|
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
|
|
|
|
{"127.0.0.3", 18008, corepb.HealthStatus_HEALTHY},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
cluster: "endpoint-1.web", qtype: dns.TypeA, rcode: dns.RcodeSuccess, answer: "127.0.0.3",
|
|
|
|
|
},
|
2019-10-05 11:45:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
|
|
for i, tc := range tests {
|
|
|
|
|
a := xds.NewAssignment()
|
|
|
|
|
a.SetClusterLoadAssignment("web", tc.cla) // web is our cluster
|
|
|
|
|
c.SetAssignments(a)
|
|
|
|
|
|
|
|
|
|
m := new(dns.Msg)
|
|
|
|
|
cl := dnsutil.Join(tc.cluster, tr.origins[0])
|
|
|
|
|
m.SetQuestion(cl, tc.qtype)
|
|
|
|
|
|
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
|
|
|
_, err := tr.ServeDNS(ctx, rec, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Test %d: Expected no error, but got %q", i, err)
|
|
|
|
|
}
|
|
|
|
|
if rec.Msg.Rcode != tc.rcode {
|
|
|
|
|
t.Errorf("Test %d: Expected no rcode %d, but got %d", i, tc.rcode, rec.Msg.Rcode)
|
|
|
|
|
}
|
|
|
|
|
if tc.ns && len(rec.Msg.Ns) == 0 {
|
|
|
|
|
t.Errorf("Test %d: Expected authority section, but got none", i)
|
|
|
|
|
}
|
|
|
|
|
if tc.answer != "" && len(rec.Msg.Answer) == 0 {
|
|
|
|
|
t.Fatalf("Test %d: Expected answer section, but got none", i)
|
|
|
|
|
}
|
|
|
|
|
if tc.answer != "" {
|
|
|
|
|
record := rec.Msg.Answer[0]
|
|
|
|
|
addr := ""
|
|
|
|
|
switch x := record.(type) {
|
|
|
|
|
case *dns.A:
|
|
|
|
|
addr = x.A.String()
|
|
|
|
|
case *dns.AAAA:
|
|
|
|
|
addr = x.AAAA.String()
|
2020-01-18 20:12:25 +01:00
|
|
|
case *dns.SRV:
|
|
|
|
|
addr = x.Target
|
2019-10-05 11:45:45 +01:00
|
|
|
}
|
|
|
|
|
if tc.answer != addr {
|
|
|
|
|
t.Errorf("Test %d: Expected answer %s, but got %s", i, tc.answer, addr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 13:48:09 +01:00
|
|
|
func TestTrafficLocality(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."}}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
2020-01-31 07:36:35 +01:00
|
|
|
cla *endpointpb.ClusterLoadAssignment
|
2020-01-25 08:19:00 +01:00
|
|
|
loc xds.Locality // where we run
|
|
|
|
|
answer string
|
2020-01-24 13:48:09 +01:00
|
|
|
}{
|
|
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2020-01-24 13:48:09 +01:00
|
|
|
ClusterName: "web",
|
2020-01-24 20:51:40 +01:00
|
|
|
Endpoints: append(
|
2020-01-31 07:36:35 +01:00
|
|
|
// IPs here should be different, but locality isn't implemented. Make
|
|
|
|
|
// them identical so the test doesn't fail...(for now)
|
2020-01-24 20:51:40 +01:00
|
|
|
endpointsWithLocality([]EndpointHealth{
|
2020-01-31 07:36:35 +01:00
|
|
|
{"127.0.1.1", 18008, corepb.HealthStatus_HEALTHY},
|
|
|
|
|
{"127.0.1.1", 18008, corepb.HealthStatus_HEALTHY}},
|
2020-01-24 20:51:40 +01:00
|
|
|
xds.Locality{Region: "us"}),
|
2020-01-24 13:48:09 +01:00
|
|
|
endpointsWithLocality([]EndpointHealth{
|
2020-01-25 08:19:00 +01:00
|
|
|
{"127.0.1.1", 18008, corepb.HealthStatus_HEALTHY}},
|
2020-01-24 20:51:40 +01:00
|
|
|
xds.Locality{Region: "eu"})...,
|
|
|
|
|
),
|
2020-01-24 13:48:09 +01:00
|
|
|
},
|
2020-01-25 08:19:00 +01:00
|
|
|
answer: "127.0.1.1",
|
|
|
|
|
loc: xds.Locality{Region: "eu"}, // our location
|
2020-01-24 13:48:09 +01:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
|
|
for i, tc := range tests {
|
|
|
|
|
a := xds.NewAssignment()
|
2020-01-25 08:19:00 +01:00
|
|
|
a.SetClusterLoadAssignment("web", tc.cla)
|
2020-01-24 13:48:09 +01:00
|
|
|
c.SetAssignments(a)
|
|
|
|
|
|
2020-01-25 08:19:00 +01:00
|
|
|
m := new(dns.Msg).SetQuestion(dnsutil.Join("web", tr.origins[0]), dns.TypeA)
|
2020-01-24 13:48:09 +01:00
|
|
|
|
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
|
|
|
_, err := tr.ServeDNS(ctx, rec, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Test %d: Expected no error, but got %q", i, err)
|
|
|
|
|
}
|
2020-01-25 08:19:00 +01:00
|
|
|
if x := rec.Msg.Answer[0].(*dns.A).A.String(); x != tc.answer {
|
|
|
|
|
t.Fatalf("Test %d: Expected %s, but got %s", i, tc.answer, x)
|
2020-01-24 13:48:09 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-19 08:30:13 +01:00
|
|
|
func TestTrafficSRV(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."}}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
2020-01-31 07:36:35 +01:00
|
|
|
cla *endpointpb.ClusterLoadAssignment
|
2020-01-19 08:30:13 +01:00
|
|
|
cluster string
|
|
|
|
|
qtype uint16
|
|
|
|
|
rcode int
|
|
|
|
|
answer int // number of records in answer section
|
|
|
|
|
}{
|
|
|
|
|
// SRV query healthy endpoint
|
|
|
|
|
{
|
2020-01-31 07:36:35 +01:00
|
|
|
cla: &endpointpb.ClusterLoadAssignment{
|
2020-01-19 08:30:13 +01:00
|
|
|
ClusterName: "web",
|
|
|
|
|
Endpoints: endpoints([]EndpointHealth{
|
|
|
|
|
{"127.0.0.2", 18008, corepb.HealthStatus_HEALTHY},
|
|
|
|
|
{"127.0.0.3", 18008, corepb.HealthStatus_HEALTHY},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
cluster: "web", qtype: dns.TypeSRV, rcode: dns.RcodeSuccess, answer: 2,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
|
|
for i, tc := range tests {
|
|
|
|
|
a := xds.NewAssignment()
|
|
|
|
|
a.SetClusterLoadAssignment("web", tc.cla) // web is our cluster
|
|
|
|
|
c.SetAssignments(a)
|
|
|
|
|
|
|
|
|
|
m := new(dns.Msg)
|
|
|
|
|
cl := dnsutil.Join(tc.cluster, tr.origins[0])
|
|
|
|
|
m.SetQuestion(cl, tc.qtype)
|
|
|
|
|
|
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
|
|
|
_, err := tr.ServeDNS(ctx, rec, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("Test %d: Expected no error, but got %q", i, err)
|
|
|
|
|
}
|
|
|
|
|
if rec.Msg.Rcode != tc.rcode {
|
|
|
|
|
t.Errorf("Test %d: Expected no rcode %d, but got %d", i, tc.rcode, rec.Msg.Rcode)
|
|
|
|
|
}
|
|
|
|
|
if tc.answer != len(rec.Msg.Answer) {
|
|
|
|
|
t.Fatalf("Test %d: Expected %d answers, but got %d", i, tc.answer, len(rec.Msg.Answer))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-04 15:38:29 +01:00
|
|
|
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 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-05 11:45:45 +01:00
|
|
|
type EndpointHealth struct {
|
|
|
|
|
Address string
|
2020-01-18 20:12:25 +01:00
|
|
|
Port uint16
|
2019-10-05 11:45:45 +01:00
|
|
|
Health corepb.HealthStatus
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func endpoints(e []EndpointHealth) []*endpointpb.LocalityLbEndpoints {
|
2020-01-24 13:34:59 +01:00
|
|
|
return endpointsWithLocality(e, xds.Locality{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func endpointsWithLocality(e []EndpointHealth, loc xds.Locality) []*endpointpb.LocalityLbEndpoints {
|
2019-10-05 11:45:45 +01:00
|
|
|
ep := make([]*endpointpb.LocalityLbEndpoints, len(e))
|
|
|
|
|
for i := range e {
|
|
|
|
|
ep[i] = &endpointpb.LocalityLbEndpoints{
|
2020-01-24 13:34:59 +01:00
|
|
|
Locality: &corepb.Locality{
|
|
|
|
|
Region: loc.Region,
|
|
|
|
|
Zone: loc.Zone,
|
|
|
|
|
SubZone: loc.SubZone,
|
|
|
|
|
},
|
2019-10-05 11:45:45 +01:00
|
|
|
LbEndpoints: []*endpointpb.LbEndpoint{{
|
|
|
|
|
HostIdentifier: &endpointpb.LbEndpoint_Endpoint{
|
|
|
|
|
Endpoint: &endpointpb.Endpoint{
|
|
|
|
|
Address: &corepb.Address{
|
|
|
|
|
Address: &corepb.Address_SocketAddress{
|
|
|
|
|
SocketAddress: &corepb.SocketAddress{
|
|
|
|
|
Address: e[i].Address,
|
2020-01-18 20:12:25 +01:00
|
|
|
PortSpecifier: &corepb.SocketAddress_PortValue{
|
|
|
|
|
PortValue: uint32(e[i].Port),
|
|
|
|
|
},
|
2019-10-05 11:45:45 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
HealthStatus: e[i].Health,
|
|
|
|
|
}},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ep
|
|
|
|
|
}
|