aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugin/kubernetes/README.md14
-rw-r--r--plugin/kubernetes/handler_test.go7
-rw-r--r--plugin/kubernetes/metadata.go59
-rw-r--r--plugin/kubernetes/metadata_test.go126
-rw-r--r--plugin/metadata/metadata.go7
-rw-r--r--plugin/test/responsewriter.go11
6 files changed, 218 insertions, 6 deletions
diff --git a/plugin/kubernetes/README.md b/plugin/kubernetes/README.md
index 22614db8e..68efdae0c 100644
--- a/plugin/kubernetes/README.md
+++ b/plugin/kubernetes/README.md
@@ -225,3 +225,17 @@ or the word "any"), then that label will match all values. The labels that acce
*.service.default.svc.cluster.local. 5 IN A 192.168.25.15
```
This response can be randomized using the `loadbalance` plugin
+
+## Metadata
+
+The kubernetes plugin will publish the following metadata, if the _metadata_
+plugin is also enabled:
+
+ * kubernetes/endpoint: the endpoint name in the query
+ * kubernetes/kind: the resource kind (pod or svc) in the query
+ * kubernetes/namespace: the namespace in the query
+ * kubernetes/port-name: the port name in an SRV query
+ * kubernetes/protocol: the protocol in an SRV query
+ * kubernetes/service: the service name in the query
+ * kubernetes/client-namespace: the client pod's namespace, if `pods verified` mode is enabled
+ * kubernetes/client-pod-name: the client pod's name, if `pods verified` mode is enabled
diff --git a/plugin/kubernetes/handler_test.go b/plugin/kubernetes/handler_test.go
index e1a8212ca..0efd03c07 100644
--- a/plugin/kubernetes/handler_test.go
+++ b/plugin/kubernetes/handler_test.go
@@ -495,9 +495,12 @@ func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil
func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) Modified() int64 { return time.Now().Unix() }
-func (APIConnServeTest) PodIndex(string) []*object.Pod {
+func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
+ if ip != "10.240.0.1" {
+ return []*object.Pod{}
+ }
a := []*object.Pod{
- {Namespace: "podns", PodIP: "10.240.0.1"}, // Remote IP set in test.ResponseWriter
+ {Namespace: "podns", Name: "foo", PodIP: "10.240.0.1"}, // Remote IP set in test.ResponseWriter
}
return a
}
diff --git a/plugin/kubernetes/metadata.go b/plugin/kubernetes/metadata.go
new file mode 100644
index 000000000..323ae9e11
--- /dev/null
+++ b/plugin/kubernetes/metadata.go
@@ -0,0 +1,59 @@
+package kubernetes
+
+import (
+ "context"
+
+ "github.com/coredns/coredns/plugin/metadata"
+ "github.com/coredns/coredns/request"
+)
+
+// Metadata implements the metadata.Provider interface.
+func (k *Kubernetes) Metadata(ctx context.Context, state request.Request) context.Context {
+ // possible optimization: cache r so it doesn't need to be calculated again in ServeDNS
+ r, err := parseRequest(state)
+ if err != nil {
+ metadata.SetValueFunc(ctx, "kubernetes/parse-error", func() string {
+ return err.Error()
+ })
+ return ctx
+ }
+
+ metadata.SetValueFunc(ctx, "kubernetes/port-name", func() string {
+ return r.port
+ })
+
+ metadata.SetValueFunc(ctx, "kubernetes/protocol", func() string {
+ return r.protocol
+ })
+
+ metadata.SetValueFunc(ctx, "kubernetes/endpoint", func() string {
+ return r.endpoint
+ })
+
+ metadata.SetValueFunc(ctx, "kubernetes/service", func() string {
+ return r.service
+ })
+
+ metadata.SetValueFunc(ctx, "kubernetes/namespace", func() string {
+ return r.namespace
+ })
+
+ metadata.SetValueFunc(ctx, "kubernetes/kind", func() string {
+ return r.podOrSvc
+ })
+
+ pod := k.podWithIP(state.IP())
+ if pod == nil {
+ return ctx
+ }
+
+ metadata.SetValueFunc(ctx, "kubernetes/client-namespace", func() string {
+ return pod.Namespace
+ })
+
+ metadata.SetValueFunc(ctx, "kubernetes/client-pod-name", func() string {
+ return pod.Name
+ })
+
+ return ctx
+}
diff --git a/plugin/kubernetes/metadata_test.go b/plugin/kubernetes/metadata_test.go
new file mode 100644
index 000000000..44f16f13e
--- /dev/null
+++ b/plugin/kubernetes/metadata_test.go
@@ -0,0 +1,126 @@
+package kubernetes
+
+import (
+ "context"
+ "testing"
+
+ "github.com/coredns/coredns/plugin/metadata"
+ "github.com/coredns/coredns/plugin/test"
+ "github.com/coredns/coredns/request"
+
+ "github.com/miekg/dns"
+)
+
+var metadataCases = []struct {
+ Qname string
+ Qtype uint16
+ RemoteIP string
+ Md map[string]string
+}{
+ {
+ Qname: "foo.bar.notapod.cluster.local.", Qtype: dns.TypeA,
+ Md: map[string]string{
+ "kubernetes/parse-error": "invalid query name",
+ },
+ },
+ {
+ Qname: "10-240-0-1.podns.pod.cluster.local.", Qtype: dns.TypeA,
+ Md: map[string]string{
+ "kubernetes/endpoint": "",
+ "kubernetes/kind": "pod",
+ "kubernetes/namespace": "podns",
+ "kubernetes/port-name": "*",
+ "kubernetes/protocol": "*",
+ "kubernetes/service": "10-240-0-1",
+ "kubernetes/client-namespace": "podns",
+ "kubernetes/client-pod-name": "foo",
+ },
+ },
+ {
+ Qname: "s.ns.svc.cluster.local.", Qtype: dns.TypeA,
+ Md: map[string]string{
+ "kubernetes/endpoint": "",
+ "kubernetes/kind": "svc",
+ "kubernetes/namespace": "ns",
+ "kubernetes/port-name": "*",
+ "kubernetes/protocol": "*",
+ "kubernetes/service": "s",
+ "kubernetes/client-namespace": "podns",
+ "kubernetes/client-pod-name": "foo",
+ },
+ },
+ {
+ Qname: "s.ns.svc.cluster.local.", Qtype: dns.TypeA,
+ RemoteIP: "10.10.10.10",
+ Md: map[string]string{
+ "kubernetes/endpoint": "",
+ "kubernetes/kind": "svc",
+ "kubernetes/namespace": "ns",
+ "kubernetes/port-name": "*",
+ "kubernetes/protocol": "*",
+ "kubernetes/service": "s",
+ },
+ },
+ {
+ Qname: "_http._tcp.s.ns.svc.cluster.local.", Qtype: dns.TypeSRV,
+ RemoteIP: "10.10.10.10",
+ Md: map[string]string{
+ "kubernetes/endpoint": "",
+ "kubernetes/kind": "svc",
+ "kubernetes/namespace": "ns",
+ "kubernetes/port-name": "http",
+ "kubernetes/protocol": "tcp",
+ "kubernetes/service": "s",
+ },
+ },
+ {
+ Qname: "ep.s.ns.svc.cluster.local.", Qtype: dns.TypeA,
+ RemoteIP: "10.10.10.10",
+ Md: map[string]string{
+ "kubernetes/endpoint": "ep",
+ "kubernetes/kind": "svc",
+ "kubernetes/namespace": "ns",
+ "kubernetes/port-name": "*",
+ "kubernetes/protocol": "*",
+ "kubernetes/service": "s",
+ },
+ },
+}
+
+func mapsDiffer(a, b map[string]string) bool {
+ if len(a) != len(b) {
+ return true
+ }
+
+ for k, va := range a {
+ vb, ok := b[k]
+ if !ok || va != vb {
+ return true
+ }
+ }
+ return false
+}
+
+func TestMetadata(t *testing.T) {
+ k := New([]string{"cluster.local."})
+ k.APIConn = &APIConnServeTest{}
+
+ for i, tc := range metadataCases {
+ ctx := metadata.ContextWithMetadata(context.Background())
+ state := request.Request{
+ Req: &dns.Msg{Question: []dns.Question{{Name: tc.Qname, Qtype: tc.Qtype}}},
+ Zone: "cluster.local.",
+ W: &test.ResponseWriter{RemoteIP: tc.RemoteIP},
+ }
+
+ k.Metadata(ctx, state)
+
+ md := make(map[string]string)
+ for _, l := range metadata.Labels(ctx) {
+ md[l] = metadata.ValueFunc(ctx, l)()
+ }
+ if mapsDiffer(tc.Md, md) {
+ t.Errorf("case %d expected metadata %v and got %v", i, tc.Md, md)
+ }
+ }
+}
diff --git a/plugin/metadata/metadata.go b/plugin/metadata/metadata.go
index 4abe57ddf..b3f803eab 100644
--- a/plugin/metadata/metadata.go
+++ b/plugin/metadata/metadata.go
@@ -20,10 +20,15 @@ type Metadata struct {
// Name implements the Handler interface.
func (m *Metadata) Name() string { return "metadata" }
+// ContextWithMetadata is exported for use by provider tests
+func ContextWithMetadata(ctx context.Context) context.Context {
+ return context.WithValue(ctx, key{}, md{})
+}
+
// ServeDNS implements the plugin.Handler interface.
func (m *Metadata) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
- ctx = context.WithValue(ctx, key{}, md{})
+ ctx = ContextWithMetadata(ctx)
state := request.Request{W: w, Req: r}
if plugin.Zones(m.Zones).Matches(state.Name()) != "" {
diff --git a/plugin/test/responsewriter.go b/plugin/test/responsewriter.go
index 77c014116..feaa8bd7e 100644
--- a/plugin/test/responsewriter.go
+++ b/plugin/test/responsewriter.go
@@ -10,7 +10,8 @@ import (
// remote will always be 10.240.0.1 and port 40212. The local address is always 127.0.0.1 and
// port 53.
type ResponseWriter struct {
- TCP bool // if TCP is true we return an TCP connection instead of an UDP one.
+ TCP bool // if TCP is true we return an TCP connection instead of an UDP one.
+ RemoteIP string
}
// LocalAddr returns the local address, 127.0.0.1:53 (UDP, TCP if t.TCP is true).
@@ -23,9 +24,13 @@ func (t *ResponseWriter) LocalAddr() net.Addr {
return &net.UDPAddr{IP: ip, Port: port, Zone: ""}
}
-// RemoteAddr returns the remote address, always 10.240.0.1:40212 (UDP, TCP is t.TCP is true).
+// RemoteAddr returns the remote address, defaults to 10.240.0.1:40212 (UDP, TCP is t.TCP is true).
func (t *ResponseWriter) RemoteAddr() net.Addr {
- ip := net.ParseIP("10.240.0.1")
+ remoteIP := "10.240.0.1"
+ if t.RemoteIP != "" {
+ remoteIP = t.RemoteIP
+ }
+ ip := net.ParseIP(remoteIP)
port := 40212
if t.TCP {
return &net.TCPAddr{IP: ip, Port: port, Zone: ""}