mirror of
https://github.com/coredns/coredns.git
synced 2025-12-20 00:55:11 -05:00
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>
This commit is contained in:
333
plugin/forwardcrd/controller_test.go
Normal file
333
plugin/forwardcrd/controller_test.go
Normal file
@@ -0,0 +1,333 @@
|
||||
package forwardcrd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coredns/coredns/plugin/forward"
|
||||
corednsv1alpha1 "github.com/coredns/coredns/plugin/forwardcrd/apis/coredns/v1alpha1"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/dynamic/fake"
|
||||
)
|
||||
|
||||
func TestCreateForward(t *testing.T) {
|
||||
controller, client, testPluginInstancer, pluginInstanceMap := setupControllerTestcase(t, "")
|
||||
forward := &corednsv1alpha1.Forward{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-dns-zone",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corednsv1alpha1.ForwardSpec{
|
||||
From: "crd.test",
|
||||
To: []string{"127.0.0.2", "127.0.0.3"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.Resource(corednsv1alpha1.GroupVersion.WithResource("forwards")).
|
||||
Namespace("default").
|
||||
Create(context.Background(), mustForwardToUnstructured(forward), metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected not to error: %s", err)
|
||||
}
|
||||
|
||||
err = wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
return testPluginInstancer.NewWithConfigCallCount() == 1, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected plugin instance to have been called: %s", err)
|
||||
}
|
||||
|
||||
handler := testPluginInstancer.NewWithConfigArgsForCall(0)
|
||||
if handler.ReceivedConfig.From != "crd.test" {
|
||||
t.Fatalf("Expected plugin to be created for zone: %s but was: %s", "crd.test", handler.ReceivedConfig.From)
|
||||
}
|
||||
|
||||
if len(handler.ReceivedConfig.To) != 2 {
|
||||
t.Fatalf("Expected plugin to contain exactly 2 servers to forward to but contains: %#v", handler.ReceivedConfig.To)
|
||||
}
|
||||
|
||||
if handler.ReceivedConfig.To[0] != "127.0.0.2" {
|
||||
t.Fatalf("Expected plugin to be created to forward to: %s but was: %s", "127.0.0.2", handler.ReceivedConfig.To[0])
|
||||
}
|
||||
|
||||
if handler.ReceivedConfig.To[1] != "127.0.0.3" {
|
||||
t.Fatalf("Expected plugin to be created to forward to: %s but was: %s", "127.0.0.3", handler.ReceivedConfig.To[1])
|
||||
}
|
||||
|
||||
pluginHandler, ok := pluginInstanceMap.Get("crd.test")
|
||||
if !ok {
|
||||
t.Fatal("Expected plugin lookup to succeed")
|
||||
}
|
||||
|
||||
if pluginHandler != handler {
|
||||
t.Fatalf("Exepcted plugin lookup to match what the instancer provided: %#v but was %#v", handler, pluginHandler)
|
||||
}
|
||||
|
||||
if testPluginInstancer.testPluginHandlers[0].OnStartupCallCount() != 1 {
|
||||
t.Fatalf("Expected plugin OnStartup to have been called once, but got: %d", testPluginInstancer.testPluginHandlers[0].OnStartupCallCount())
|
||||
}
|
||||
|
||||
if err := controller.Stop(); err != nil {
|
||||
t.Fatalf("Expected no error: %s", err)
|
||||
}
|
||||
|
||||
err = wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
return testPluginInstancer.testPluginHandlers[0].OnShutdownCallCount() == 1, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected plugin OnShutdown to have been called once, but got: %d", testPluginInstancer.testPluginHandlers[0].OnShutdownCallCount())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateForward(t *testing.T) {
|
||||
controller, client, testPluginInstancer, pluginInstanceMap := setupControllerTestcase(t, "")
|
||||
forward := &corednsv1alpha1.Forward{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-dns-zone",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corednsv1alpha1.ForwardSpec{
|
||||
From: "crd.test",
|
||||
To: []string{"127.0.0.2"},
|
||||
},
|
||||
}
|
||||
|
||||
unstructuredForward, err := client.Resource(corednsv1alpha1.GroupVersion.WithResource("forwards")).
|
||||
Namespace("default").
|
||||
Create(context.Background(), mustForwardToUnstructured(forward), metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected not to error: %s", err)
|
||||
}
|
||||
|
||||
err = wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
return testPluginInstancer.NewWithConfigCallCount() == 1, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected plugin instance to have been called: %s", err)
|
||||
}
|
||||
|
||||
forward = mustUnstructuredToForward(unstructuredForward)
|
||||
forward.Spec.From = "other.test"
|
||||
forward.Spec.To = []string{"127.0.0.3"}
|
||||
|
||||
_, err = client.Resource(corednsv1alpha1.GroupVersion.WithResource("forwards")).
|
||||
Namespace("default").
|
||||
Update(context.Background(), mustForwardToUnstructured(forward), metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected not to error: %s", err)
|
||||
}
|
||||
|
||||
err = wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
return testPluginInstancer.NewWithConfigCallCount() == 2, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected plugin instance to have been called: %s", err)
|
||||
}
|
||||
|
||||
handler := testPluginInstancer.NewWithConfigArgsForCall(1)
|
||||
if handler.ReceivedConfig.From != "other.test" {
|
||||
t.Fatalf("Expected plugin to be created for zone: %s but was: %s", "other.test", handler.ReceivedConfig.From)
|
||||
}
|
||||
|
||||
if len(handler.ReceivedConfig.To) != 1 {
|
||||
t.Fatalf("Expected plugin to contain exactly 1 server to forward to but contains: %#v", handler.ReceivedConfig.To)
|
||||
}
|
||||
|
||||
if handler.ReceivedConfig.To[0] != "127.0.0.3" {
|
||||
t.Fatalf("Expected plugin to be created to forward to: %s but was: %s", "127.0.0.3", handler.ReceivedConfig.To[0])
|
||||
}
|
||||
|
||||
pluginHandler, ok := pluginInstanceMap.Get("other.test")
|
||||
if !ok {
|
||||
t.Fatal("Expected plugin lookup to succeed")
|
||||
}
|
||||
|
||||
if pluginHandler != handler {
|
||||
t.Fatalf("Exepcted plugin lookup to match what the instancer provided: %#v but was %#v", handler, pluginHandler)
|
||||
}
|
||||
|
||||
_, ok = pluginInstanceMap.Get("crd.test")
|
||||
if ok {
|
||||
t.Fatal("Expected lookup for crd.test to fail")
|
||||
}
|
||||
|
||||
if testPluginInstancer.testPluginHandlers[0].OnShutdownCallCount() != 1 {
|
||||
t.Fatalf("Expected plugin OnShutdown to have been called once, but got: %d", testPluginInstancer.testPluginHandlers[0].OnShutdownCallCount())
|
||||
}
|
||||
|
||||
if err := controller.Stop(); err != nil {
|
||||
t.Fatalf("Expected no error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteForward(t *testing.T) {
|
||||
controller, client, testPluginInstancer, pluginInstanceMap := setupControllerTestcase(t, "")
|
||||
forward := &corednsv1alpha1.Forward{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-dns-zone",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corednsv1alpha1.ForwardSpec{
|
||||
From: "crd.test",
|
||||
To: []string{"127.0.0.2"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.Resource(corednsv1alpha1.GroupVersion.WithResource("forwards")).
|
||||
Namespace("default").
|
||||
Create(context.Background(), mustForwardToUnstructured(forward), metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected not to error: %s", err)
|
||||
}
|
||||
|
||||
err = wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
return testPluginInstancer.NewWithConfigCallCount() == 1, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected plugin instance to have been called: %s", err)
|
||||
}
|
||||
|
||||
err = client.Resource(corednsv1alpha1.GroupVersion.WithResource("forwards")).
|
||||
Namespace("default").
|
||||
Delete(context.Background(), "test-dns-zone", metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected not to error: %s", err)
|
||||
}
|
||||
|
||||
err = wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
_, ok := pluginInstanceMap.Get("crd.test")
|
||||
return !ok, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected lookup for crd.test to fail: %s", err)
|
||||
}
|
||||
|
||||
if testPluginInstancer.testPluginHandlers[0].OnShutdownCallCount() != 1 {
|
||||
t.Fatalf("Expected plugin OnShutdown to have been called once, but got: %d", testPluginInstancer.testPluginHandlers[0].OnShutdownCallCount())
|
||||
}
|
||||
|
||||
if err := controller.Stop(); err != nil {
|
||||
t.Fatalf("Expected no error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardLimitNamespace(t *testing.T) {
|
||||
controller, client, testPluginInstancer, pluginInstanceMap := setupControllerTestcase(t, "kube-system")
|
||||
forward := &corednsv1alpha1.Forward{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-dns-zone",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corednsv1alpha1.ForwardSpec{
|
||||
From: "crd.test",
|
||||
To: []string{"127.0.0.2"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.Resource(corednsv1alpha1.GroupVersion.WithResource("forwards")).
|
||||
Namespace("default").
|
||||
Create(context.Background(), mustForwardToUnstructured(forward), metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected not to error: %s", err)
|
||||
}
|
||||
|
||||
kubeSystemForward := &corednsv1alpha1.Forward{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "system-dns-zone",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Spec: corednsv1alpha1.ForwardSpec{
|
||||
From: "system.test",
|
||||
To: []string{"127.0.0.3"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = client.Resource(corednsv1alpha1.GroupVersion.WithResource("forwards")).
|
||||
Namespace("kube-system").
|
||||
Create(context.Background(), mustForwardToUnstructured(kubeSystemForward), metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected not to error: %s", err)
|
||||
}
|
||||
|
||||
err = wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
return testPluginInstancer.NewWithConfigCallCount() == 1, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected plugin instance to have been called exactly once: %s, plugin instance call count: %d", err, testPluginInstancer.NewWithConfigCallCount())
|
||||
}
|
||||
|
||||
handler := testPluginInstancer.NewWithConfigArgsForCall(0)
|
||||
if handler.ReceivedConfig.From != "system.test" {
|
||||
t.Fatalf("Expected plugin to be created for zone: %s but was: %s", "system.test", handler.ReceivedConfig.From)
|
||||
}
|
||||
|
||||
_, ok := pluginInstanceMap.Get("system.test")
|
||||
if !ok {
|
||||
t.Fatal("Expected plugin lookup to succeed")
|
||||
}
|
||||
|
||||
_, ok = pluginInstanceMap.Get("crd.test")
|
||||
if ok {
|
||||
t.Fatal("Expected plugin lookup to fail")
|
||||
}
|
||||
|
||||
if err := controller.Stop(); err != nil {
|
||||
t.Fatalf("Expected no error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupControllerTestcase(t *testing.T, namespace string) (forwardCRDController, *fake.FakeDynamicClient, *TestPluginInstancer, *PluginInstanceMap) {
|
||||
scheme := runtime.NewScheme()
|
||||
scheme.AddKnownTypes(corednsv1alpha1.GroupVersion, &corednsv1alpha1.Forward{})
|
||||
customListKinds := map[schema.GroupVersionResource]string{
|
||||
corednsv1alpha1.GroupVersion.WithResource("forwards"): "ForwardList",
|
||||
}
|
||||
client := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, customListKinds)
|
||||
pluginMap := NewPluginInstanceMap()
|
||||
testPluginInstancer := &TestPluginInstancer{}
|
||||
controller := newForwardCRDController(context.Background(), client, scheme, namespace, pluginMap, func(cfg forward.ForwardConfig) (lifecyclePluginHandler, error) {
|
||||
return testPluginInstancer.NewWithConfig(cfg)
|
||||
})
|
||||
|
||||
go controller.Run(1)
|
||||
|
||||
err := wait.Poll(time.Second, time.Second*5, func() (bool, error) {
|
||||
return controller.HasSynced(), nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected controller to have synced: %s", err)
|
||||
}
|
||||
|
||||
return controller, client, testPluginInstancer, pluginMap
|
||||
}
|
||||
|
||||
func mustForwardToUnstructured(forward *corednsv1alpha1.Forward) *unstructured.Unstructured {
|
||||
forward.TypeMeta = metav1.TypeMeta{
|
||||
Kind: "Forward",
|
||||
APIVersion: "coredns.io/v1alpha1",
|
||||
}
|
||||
|
||||
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(forward)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("coding error: unable to convert to unstructured: %s", err))
|
||||
}
|
||||
return &unstructured.Unstructured{
|
||||
Object: obj,
|
||||
}
|
||||
}
|
||||
|
||||
func mustUnstructuredToForward(obj *unstructured.Unstructured) *corednsv1alpha1.Forward {
|
||||
forward := &corednsv1alpha1.Forward{}
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, forward)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("coding error: unable to convert from unstructured: %s", err))
|
||||
}
|
||||
return forward
|
||||
}
|
||||
Reference in New Issue
Block a user