coredns/plugin/forwardcrd/forwardcrd_test.go
Christian Ang 2e6953c7db
Initial implementation of ForwardCRD plugin (#4512)
* Add forwardcrd plugin README.md

Co-authored-by: Aidan Obley <aobley@vmware.com>

Signed-off-by: Christian Ang <angc@vmware.com>

* Create forwardcrd plugin

- Place forwardcrd before forward plugin in plugin list. This will avoid
forward from preventing the forwardcrd plugin from handling any queries
in the case of having a default upstream forwarder in a server block (as
is the case in the default kubernetes Corefile).

Co-authored-by: Aidan Obley <aobley@vmware.com>

Signed-off-by: Christian Ang <angc@vmware.com>

* Add Forward CRD

Signed-off-by: Christian Ang <angc@vmware.com>

* Add NewWithConfig to forward plugin

- allows external packages to instanciate forward plugins

Co-authored-by: Aidan Obley <aobley@vmware.com>

Signed-off-by: Christian Ang <angc@vmware.com>

* ForwardCRD plugin handles requests for Forward CRs

- add a Kubernetes controller that can read Forward CRs
- instances of the forward plugin are created based on Forward CRs from
the Kubernetes controller
- DNS requests are handled by calling matching Forward plugin instances
based on zone name
- Defaults to the kube-system namespace to align with Corefile RBAC

Signed-off-by: Christian Ang <angc@vmware.com>

Use klog v2 in forwardcrd plugin

* Refactor forward setup to use NewWithConfig

Co-authored-by: Christian Ang <angc@vmware.com>

Signed-off-by: Edwin Xie <exie@vmware.com>

* Use ParseInt instead of Atoi

- to ensure that the bitsize is 32 for later casting to uint32

Signed-off-by: Christian Ang <angc@vmware.com>

* Add @christianang to CODEOWNERS for forwardcrd

Signed-off-by: Christian Ang <angc@vmware.com>

Co-authored-by: Edwin Xie <exie@vmware.com>
2021-11-12 11:22:34 -05:00

183 lines
5.2 KiB
Go

package forwardcrd
import (
"context"
"fmt"
"strings"
"testing"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin/forward"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
)
func TestDNSRequestForZone(t *testing.T) {
k, closeAll := setupForwardCRDTestcase(t, "")
defer closeAll()
m := new(dns.Msg)
m.SetQuestion("crd.test.", dns.TypeA)
rec := dnstest.NewRecorder(&test.ResponseWriter{})
if _, err := k.ServeDNS(context.Background(), rec, m); err != nil {
t.Fatalf("Expected not to error: %s", err)
}
if rec.Msg == nil || len(rec.Msg.Answer) != 1 {
t.Fatal("Expected an answer")
}
if x := rec.Msg.Answer[0].Header().Name; x != "crd.test." {
t.Fatalf("Expected answer header name to be: %s, but got: %s", "crd.test.", x)
}
if x := rec.Msg.Answer[0].(*dns.A).A.String(); x != "1.2.3.4" {
t.Fatalf("Expected answer ip to be: %s, but got: %s", "1.2.3.4", x)
}
m = new(dns.Msg)
m.SetQuestion("other.test.", dns.TypeA)
rec = dnstest.NewRecorder(&test.ResponseWriter{})
if _, err := k.ServeDNS(context.Background(), rec, m); err != nil {
t.Fatalf("Expected not to error: %s", err)
}
if rec.Msg == nil || len(rec.Msg.Answer) != 1 {
t.Fatal("Expected an answer")
}
if x := rec.Msg.Answer[0].Header().Name; x != "other.test." {
t.Fatalf("Expected answer header name to be: %s, but got: %s", "other.test.", x)
}
if x := rec.Msg.Answer[0].(*dns.A).A.String(); x != "1.2.3.4" {
t.Fatalf("Expected answer ip to be: %s, but got: %s", "1.2.3.4", x)
}
}
func TestDNSRequestForSubdomain(t *testing.T) {
k, closeAll := setupForwardCRDTestcase(t, "")
defer closeAll()
m := new(dns.Msg)
m.SetQuestion("foo.crd.test.", dns.TypeA)
rec := dnstest.NewRecorder(&test.ResponseWriter{})
if _, err := k.ServeDNS(context.Background(), rec, m); err != nil {
t.Fatalf("Expected not to error: %s", err)
}
if rec.Msg == nil || len(rec.Msg.Answer) != 1 {
t.Fatal("Expected an answer")
}
if x := rec.Msg.Answer[0].Header().Name; x != "foo.crd.test." {
t.Fatalf("Expected answer header name to be: %s, but got: %s", "foo.crd.test.", x)
}
if x := rec.Msg.Answer[0].(*dns.A).A.String(); x != "1.2.3.4" {
t.Fatalf("Expected answer ip to be: %s, but got: %s", "1.2.3.4", x)
}
}
func TestDNSRequestForNonexistantZone(t *testing.T) {
k, closeAll := setupForwardCRDTestcase(t, "")
defer closeAll()
m := new(dns.Msg)
m.SetQuestion("non-existant-zone.test.", dns.TypeA)
rec := dnstest.NewRecorder(&test.ResponseWriter{})
if rcode, err := k.ServeDNS(context.Background(), rec, m); err == nil || rcode != dns.RcodeServerFailure {
t.Fatalf("Expected to return rcode: %d and to error: %s", rcode, err)
}
}
func TestDNSRequestForLimitedZones(t *testing.T) {
k, closeAll := setupForwardCRDTestcase(t, "crd.test.")
defer closeAll()
m := new(dns.Msg)
m.SetQuestion("crd.test.", dns.TypeA)
rec := dnstest.NewRecorder(&test.ResponseWriter{})
if _, err := k.ServeDNS(context.Background(), rec, m); err != nil {
t.Fatalf("Expected not to error: %s", err)
}
if rec.Msg == nil || len(rec.Msg.Answer) != 1 {
t.Fatal("Expected an answer")
}
m = new(dns.Msg)
m.SetQuestion("sub.crd.test.", dns.TypeA)
rec = dnstest.NewRecorder(&test.ResponseWriter{})
if _, err := k.ServeDNS(context.Background(), rec, m); err != nil {
t.Fatalf("Expected not to error: %s", err)
}
if rec.Msg == nil || len(rec.Msg.Answer) != 1 {
t.Fatal("Expected an answer")
}
m = new(dns.Msg)
m.SetQuestion("other.test.", dns.TypeA)
rec = dnstest.NewRecorder(&test.ResponseWriter{})
if rcode, err := k.ServeDNS(context.Background(), rec, m); err == nil || rcode != dns.RcodeServerFailure {
t.Fatalf("Expected to return rcode: %d and to error: %s", rcode, err)
}
m = new(dns.Msg)
m.SetQuestion("test.", dns.TypeA)
rec = dnstest.NewRecorder(&test.ResponseWriter{})
if rcode, err := k.ServeDNS(context.Background(), rec, m); err == nil || rcode != dns.RcodeServerFailure {
t.Fatalf("Expected to return rcode: %d and to error: %s", rcode, err)
}
}
func setupForwardCRDTestcase(t *testing.T, zone string) (*ForwardCRD, func()) {
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
ret := new(dns.Msg)
ret.SetReply(r)
ret.Answer = append(ret.Answer, test.A(fmt.Sprintf("%s IN A 1.2.3.4", strings.ToLower(r.Question[0].Name))))
w.WriteMsg(ret)
})
c := caddy.NewTestController("dns", fmt.Sprintf("forwardcrd %s", zone))
c.ServerBlockKeys = []string{"."}
k, err := parseForwardCRD(c)
if err != nil {
t.Errorf("Expected not to error: %s", err)
}
k.APIConn = &TestController{}
forwardCRDTest, err := forward.NewWithConfig(forward.ForwardConfig{
From: "crd.test",
To: []string{s.Addr},
})
if err != nil {
t.Errorf("Expected not to error: %s", err)
}
forwardCRDTest.OnStartup()
forwardOtherTest, err := forward.NewWithConfig(forward.ForwardConfig{
From: "other.test.",
To: []string{s.Addr},
})
if err != nil {
t.Errorf("Expected not to error: %s", err)
}
forwardOtherTest.OnStartup()
k.pluginInstanceMap.Upsert("default/crd-test", "crd.test", forwardCRDTest)
k.pluginInstanceMap.Upsert("default/other-test", "other.test", forwardOtherTest)
closeAll := func() {
s.Close()
forwardCRDTest.OnShutdown()
forwardOtherTest.OnShutdown()
}
return k, closeAll
}