Publish metadata from kubernetes plugin (#2829)

* Publish metadata from kubernetes plugin

* stickler fix

* Add a couple tests

* Add metadata section to README

* Update plugin/kubernetes/README.md

Co-Authored-By: Chris O'Haver <cohaver@infoblox.com>

* Address nit
This commit is contained in:
John Belamaric
2019-06-09 00:10:57 -07:00
committed by Miek Gieben
parent a1c97f82a6
commit ffcd2f61cf
6 changed files with 218 additions and 6 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}
}