diff --git a/plugin/k8s_external/README.md b/plugin/k8s_external/README.md index 0faaca3cf..c7df91abc 100644 --- a/plugin/k8s_external/README.md +++ b/plugin/k8s_external/README.md @@ -83,6 +83,18 @@ spec: type: ClusterIP ~~~ +The *k8s_external* plugin can be used in conjunction with the *transfer* plugin to enable +zone transfers. Notifies are not supported. + + ~~~ + . { + transfer example.org { + to * + } + kubernetes cluster.local + k8s_external example.org + } + ~~~ # See Also diff --git a/plugin/k8s_external/apex.go b/plugin/k8s_external/apex.go index 85edbea6c..0ce00a7c3 100644 --- a/plugin/k8s_external/apex.go +++ b/plugin/k8s_external/apex.go @@ -93,7 +93,7 @@ func (e *External) soa(state request.Request) *dns.SOA { soa := &dns.SOA{Hdr: header, Mbox: dnsutil.Join(e.hostmaster, e.apex, state.Zone), Ns: dnsutil.Join("ns1", e.apex, state.Zone), - Serial: 12345, // Also dynamic? + Serial: e.externalSerialFunc(state.Zone), Refresh: 7200, Retry: 1800, Expire: 86400, diff --git a/plugin/k8s_external/apex_test.go b/plugin/k8s_external/apex_test.go index 2f6923f56..45b835e3a 100644 --- a/plugin/k8s_external/apex_test.go +++ b/plugin/k8s_external/apex_test.go @@ -20,7 +20,8 @@ func TestApex(t *testing.T) { e.Zones = []string{"example.com."} e.Next = test.NextHandler(dns.RcodeSuccess, nil) e.externalFunc = k.External - e.externalAddrFunc = externalAddress // internal test function + e.externalAddrFunc = externalAddress // internal test function + e.externalSerialFunc = externalSerial // internal test function ctx := context.TODO() for i, tc := range testsApex { @@ -43,6 +44,16 @@ func TestApex(t *testing.T) { if err := test.SortAndCheck(resp, tc); err != nil { t.Error(err) } + for i, rr := range tc.Ns { + expectsoa := rr.(*dns.SOA) + gotsoa, ok := resp.Ns[i].(*dns.SOA) + if !ok { + t.Fatalf("Unexpected record type in Authority section") + } + if expectsoa.Serial != gotsoa.Serial { + t.Fatalf("Expected soa serial %d, got %d", expectsoa.Serial, gotsoa.Serial) + } + } } } @@ -50,7 +61,7 @@ var testsApex = []test.Case{ { Qname: "example.com.", Qtype: dns.TypeSOA, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ - test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), }, }, { @@ -65,37 +76,37 @@ var testsApex = []test.Case{ { Qname: "example.com.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Ns: []dns.RR{ - test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), }, }, { Qname: "dns.example.com.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Ns: []dns.RR{ - test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), }, }, { Qname: "dns.example.com.", Qtype: dns.TypeNS, Rcode: dns.RcodeSuccess, Ns: []dns.RR{ - test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), }, }, { Qname: "ns1.dns.example.com.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Ns: []dns.RR{ - test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), }, }, { Qname: "ns1.dns.example.com.", Qtype: dns.TypeNS, Rcode: dns.RcodeSuccess, Ns: []dns.RR{ - test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), }, }, { Qname: "ns1.dns.example.com.", Qtype: dns.TypeAAAA, Rcode: dns.RcodeSuccess, Ns: []dns.RR{ - test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), }, }, { diff --git a/plugin/k8s_external/external.go b/plugin/k8s_external/external.go index 720556969..c584b3414 100644 --- a/plugin/k8s_external/external.go +++ b/plugin/k8s_external/external.go @@ -29,9 +29,13 @@ type Externaler interface { External(request.Request) ([]msg.Service, int) // ExternalAddress should return a string slice of addresses for the nameserving endpoint. ExternalAddress(state request.Request) []dns.RR + // ExternalServices returns all services in the given zone as a slice of msg.Service. + ExternalServices(zone string) []msg.Service + // ExternalSerial gets the current serial. + ExternalSerial(string) uint32 } -// External resolves Ingress and Loadbalance IPs from kubernetes clusters. +// External serves records for External IPs and Loadbalance IPs of Services in Kubernetes clusters. type External struct { Next plugin.Handler Zones []string @@ -42,8 +46,10 @@ type External struct { upstream *upstream.Upstream - externalFunc func(request.Request) ([]msg.Service, int) - externalAddrFunc func(request.Request) []dns.RR + externalFunc func(request.Request) ([]msg.Service, int) + externalAddrFunc func(request.Request) []dns.RR + externalSerialFunc func(string) uint32 + externalServicesFunc func(string) []msg.Service } // New returns a new and initialized *External. diff --git a/plugin/k8s_external/external_test.go b/plugin/k8s_external/external_test.go index cc29fd7a6..7c7ee8069 100644 --- a/plugin/k8s_external/external_test.go +++ b/plugin/k8s_external/external_test.go @@ -23,7 +23,8 @@ func TestExternal(t *testing.T) { e.Zones = []string{"example.com."} e.Next = test.NextHandler(dns.RcodeSuccess, nil) e.externalFunc = k.External - e.externalAddrFunc = externalAddress // internal test function + e.externalAddrFunc = externalAddress // internal test function + e.externalSerialFunc = externalSerial // internal test function ctx := context.TODO() for i, tc := range tests { @@ -147,12 +148,38 @@ var tests = []test.Case{ test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"), }, }, + // svc11 { Qname: "svc11.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"), }, }, + { + Qname: "_http._tcp.svc11.testns.example.com.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.SRV("_http._tcp.svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."), + }, + Extra: []dns.RR{ + test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"), + }, + }, + { + Qname: "svc11.testns.example.com.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.SRV("svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."), + }, + Extra: []dns.RR{ + test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"), + }, + }, + // svc12 + { + Qname: "svc12.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.CNAME("svc12.testns.example.com. 5 IN CNAME dummy.hostname"), + }, + }, { Qname: "_http._tcp.svc12.testns.example.com.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ @@ -160,9 +187,9 @@ var tests = []test.Case{ }, }, { - Qname: "svc12.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess, + Qname: "svc12.testns.example.com.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ - test.CNAME("svc12.testns.example.com. 5 IN CNAME dummy.hostname"), + test.SRV("svc12.testns.example.com. 5 IN SRV 0 100 80 dummy.hostname."), }, }, } @@ -173,8 +200,8 @@ func (external) HasSynced() bool func (external) Run() {} func (external) Stop() error { return nil } func (external) EpIndexReverse(string) []*object.Endpoints { return nil } -func (external) SvcIndexReverse(string) []*object.Service { return nil } -func (external) Modified() int64 { return 0 } +func (external) SvcIndexReverse(string) []*object.Service { return nil } +func (external) Modified(bool) int64 { return 0 } func (external) EpIndex(s string) []*object.Endpoints { return nil } func (external) EndpointsList() []*object.Endpoints { return nil } func (external) GetNodeByName(ctx context.Context, name string) (*api.Node, error) { return nil, nil } @@ -240,3 +267,7 @@ func externalAddress(state request.Request) []dns.RR { a := test.A("example.org. IN A 127.0.0.1") return []dns.RR{a} } + +func externalSerial(string) uint32 { + return 1499347823 +} diff --git a/plugin/k8s_external/setup.go b/plugin/k8s_external/setup.go index 1f408dc80..e8a333422 100644 --- a/plugin/k8s_external/setup.go +++ b/plugin/k8s_external/setup.go @@ -26,6 +26,8 @@ func setup(c *caddy.Controller) error { if x, ok := m.(Externaler); ok { e.externalFunc = x.External e.externalAddrFunc = x.ExternalAddress + e.externalServicesFunc = x.ExternalServices + e.externalSerialFunc = x.ExternalSerial } return nil }) diff --git a/plugin/k8s_external/transfer.go b/plugin/k8s_external/transfer.go new file mode 100644 index 000000000..9c2c231f9 --- /dev/null +++ b/plugin/k8s_external/transfer.go @@ -0,0 +1,97 @@ +package external + +import ( + "context" + + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/etcd/msg" + "github.com/coredns/coredns/plugin/transfer" + "github.com/coredns/coredns/request" + + "github.com/miekg/dns" +) + +// Transfer implements transfer.Transferer +func (e *External) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) { + z := plugin.Zones(e.Zones).Matches(zone) + if z != zone { + return nil, transfer.ErrNotAuthoritative + } + + ctx := context.Background() + ch := make(chan []dns.RR, 2) + if zone == "." { + zone = "" + } + state := request.Request{Zone: zone} + + // SOA + soa := e.soa(state) + ch <- []dns.RR{soa} + if serial != 0 && serial >= soa.Serial { + close(ch) + return ch, nil + } + + go func() { + // Add NS + nsName := "ns1." + e.apex + "." + zone + nsHdr := dns.RR_Header{Name: zone, Rrtype: dns.TypeNS, Ttl: e.ttl, Class: dns.ClassINET} + ch <- []dns.RR{&dns.NS{Hdr: nsHdr, Ns: nsName}} + + // Add Nameserver A/AAAA records + nsRecords := e.externalAddrFunc(state) + for i := range nsRecords { + // externalAddrFunc returns incomplete header names, correct here + nsRecords[i].Header().Name = nsName + nsRecords[i].Header().Ttl = e.ttl + ch <- []dns.RR{nsRecords[i]} + } + + svcs := e.externalServicesFunc(zone) + srvSeen := make(map[string]struct{}) + for i := range svcs { + name := msg.Domain(svcs[i].Key) + if svcs[i].TargetStrip == 0 { + // Add Service A/AAAA records + s := request.Request{Req: &dns.Msg{Question: []dns.Question{{Name: name}}}} + as := e.a(ctx, []msg.Service{svcs[i]}, s) + if len(as) > 0 { + ch <- as + } + aaaas := e.aaaa(ctx, []msg.Service{svcs[i]}, s) + if len(aaaas) > 0 { + ch <- aaaas + } + // Add bare SRV record, ensuring uniqueness + recs, _ := e.srv(ctx, []msg.Service{svcs[i]}, s) + for _, srv := range recs { + if !nameSeen(srvSeen, srv) { + ch <- []dns.RR{srv} + } + } + continue + } + // Add full SRV record, ensuring uniqueness + s := request.Request{Req: &dns.Msg{Question: []dns.Question{{Name: name}}}} + recs, _ := e.srv(ctx, []msg.Service{svcs[i]}, s) + for _, srv := range recs { + if !nameSeen(srvSeen, srv) { + ch <- []dns.RR{srv} + } + } + } + ch <- []dns.RR{soa} + close(ch) + }() + + return ch, nil +} + +func nameSeen(namesSeen map[string]struct{}, rr dns.RR) bool { + if _, duplicate := namesSeen[rr.Header().Name]; duplicate { + return true + } + namesSeen[rr.Header().Name] = struct{}{} + return false +} diff --git a/plugin/k8s_external/transfer_test.go b/plugin/k8s_external/transfer_test.go new file mode 100644 index 000000000..c55f14c1f --- /dev/null +++ b/plugin/k8s_external/transfer_test.go @@ -0,0 +1,142 @@ +package external + +import ( + "strings" + "testing" + + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/kubernetes" + "github.com/coredns/coredns/plugin/test" + "github.com/coredns/coredns/plugin/transfer" + + "github.com/miekg/dns" +) + +func TestImplementsTransferer(t *testing.T) { + var e plugin.Handler + e = &External{} + _, ok := e.(transfer.Transferer) + if !ok { + t.Error("Transferer not implemented") + } +} + +func TestTransferAXFR(t *testing.T) { + k := kubernetes.New([]string{"cluster.local."}) + k.Namespaces = map[string]struct{}{"testns": {}} + k.APIConn = &external{} + + e := New() + e.Zones = []string{"example.com."} + e.externalFunc = k.External + e.externalAddrFunc = externalAddress // internal test function + e.externalSerialFunc = externalSerial // internal test function + e.externalServicesFunc = k.ExternalServices + + ch, err := e.Transfer("example.com.", 0) + + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + var records []dns.RR + for rrs := range ch { + records = append(records, rrs...) + } + + expect := []dns.RR{} + for _, tc := range append(tests, testsApex...) { + if tc.Rcode != dns.RcodeSuccess { + continue + } + + for _, ans := range tc.Answer { + // Exclude wildcard test cases + if strings.Contains(ans.Header().Name, "*") { + continue + } + + // Exclude TXT records + if ans.Header().Rrtype == dns.TypeTXT { + continue + } + expect = append(expect, ans) + } + } + + diff := difference(expect, records) + if len(diff) != 0 { + t.Errorf("Got back %d records that do not exist in test cases, should be 0:", len(diff)) + for _, rec := range diff { + t.Errorf("%+v", rec) + } + } + + diff = difference(records, expect) + if len(diff) != 0 { + t.Errorf("Result is missing %d records, should be 0:", len(diff)) + for _, rec := range diff { + t.Errorf("%+v", rec) + } + } + +} + +func TestTransferIXFR(t *testing.T) { + k := kubernetes.New([]string{"cluster.local."}) + k.Namespaces = map[string]struct{}{"testns": {}} + k.APIConn = &external{} + + e := New() + e.Zones = []string{"example.com."} + e.externalFunc = k.External + e.externalAddrFunc = externalAddress // internal test function + e.externalSerialFunc = externalSerial // internal test function + e.externalServicesFunc = k.ExternalServices + + ch, err := e.Transfer("example.com.", externalSerial("example.com.")) + + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + var records []dns.RR + for rrs := range ch { + records = append(records, rrs...) + } + + expect := []dns.RR{ + test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.dns.example.com. 1499347823 7200 1800 86400 5"), + } + + diff := difference(expect, records) + if len(diff) != 0 { + t.Errorf("Got back %d records that do not exist in test cases, should be 0:", len(diff)) + for _, rec := range diff { + t.Errorf("%+v", rec) + } + } + + diff = difference(records, expect) + if len(diff) != 0 { + t.Errorf("Result is missing %d records, should be 0:", len(diff)) + for _, rec := range diff { + t.Errorf("%+v", rec) + } + } + +} + +// difference shows what we're missing when comparing two RR slices +func difference(testRRs []dns.RR, gotRRs []dns.RR) []dns.RR { + expectedRRs := map[string]struct{}{} + for _, rr := range testRRs { + expectedRRs[rr.String()] = struct{}{} + } + + foundRRs := []dns.RR{} + for _, rr := range gotRRs { + if _, ok := expectedRRs[rr.String()]; !ok { + foundRRs = append(foundRRs, rr) + } + } + return foundRRs +} diff --git a/plugin/kubernetes/controller.go b/plugin/kubernetes/controller.go index f3dbff454..f2c349dba 100644 --- a/plugin/kubernetes/controller.go +++ b/plugin/kubernetes/controller.go @@ -45,15 +45,19 @@ type dnsController interface { HasSynced() bool Stop() error - // Modified returns the timestamp of the most recent changes - Modified() int64 + // Modified returns the timestamp of the most recent changes to services. If the passed bool is true, it should + // return the timestamp of the most recent changes to services with external facing IP addresses + Modified(bool) int64 } type dnsControl struct { - // Modified tracks timestamp of the most recent changes + // modified tracks timestamp of the most recent changes // It needs to be first because it is guaranteed to be 8-byte // aligned ( we use sync.LoadAtomic with this ) modified int64 + // extModified tracks timestamp of the most recent changes to + // services with external facing IP addresses + extModified int64 client kubernetes.Interface @@ -572,7 +576,13 @@ func (dns *dnsControl) detectChanges(oldObj, newObj interface{}) { } switch ob := obj.(type) { case *object.Service: - dns.updateModified() + imod, emod := serviceModified(oldObj, newObj) + if imod { + dns.updateModified() + } + if emod { + dns.updateExtModifed() + } case *object.Pod: dns.updateModified() case *object.Endpoints: @@ -646,9 +656,66 @@ func endpointsEquivalent(a, b *object.Endpoints) bool { return true } -func (dns *dnsControl) Modified() int64 { - unix := atomic.LoadInt64(&dns.modified) - return unix +// serviceModified checks the services passed for changes that result in changes +// to internal and or external records. It returns two booleans, one for internal +// record changes, and a second for external record changes +func serviceModified(oldObj, newObj interface{}) (intSvc, extSvc bool) { + if oldObj != nil && newObj == nil { + // deleted service only modifies external zone records if it had external ips + return true, len(oldObj.(*object.Service).ExternalIPs) > 0 + } + + if oldObj == nil && newObj != nil { + // added service only modifies external zone records if it has external ips + return true, len(newObj.(*object.Service).ExternalIPs) > 0 + } + + newSvc := newObj.(*object.Service) + oldSvc := oldObj.(*object.Service) + + // External IPs are mutable, affecting external zone records + if len(oldSvc.ExternalIPs) != len(newSvc.ExternalIPs) { + extSvc = true + } else { + for i := range oldSvc.ExternalIPs { + if oldSvc.ExternalIPs[i] != newSvc.ExternalIPs[i] { + extSvc = true + break + } + } + } + + // ExternalName is mutable, affecting internal zone records + intSvc = oldSvc.ExternalName != newSvc.ExternalName + + if intSvc && extSvc { + return intSvc, extSvc + } + + // All Port fields are mutable, affecting both internal/external zone records + if len(oldSvc.Ports) != len(newSvc.Ports) { + return true, true + } + for i := range oldSvc.Ports { + if oldSvc.Ports[i].Name != newSvc.Ports[i].Name { + return true, true + } + if oldSvc.Ports[i].Port != newSvc.Ports[i].Port { + return true, true + } + if oldSvc.Ports[i].Protocol != newSvc.Ports[i].Protocol { + return true, true + } + } + + return intSvc, extSvc +} + +func (dns *dnsControl) Modified(external bool) int64 { + if external { + return atomic.LoadInt64(&dns.extModified) + } + return atomic.LoadInt64(&dns.modified) } // updateModified set dns.modified to the current time. @@ -657,6 +724,12 @@ func (dns *dnsControl) updateModified() { atomic.StoreInt64(&dns.modified, unix) } +// updateExtModified set dns.extModified to the current time. +func (dns *dnsControl) updateExtModifed() { + unix := time.Now().Unix() + atomic.StoreInt64(&dns.extModified, unix) +} + var errObj = errors.New("obj was not of the correct type") const defaultResyncPeriod = 0 diff --git a/plugin/kubernetes/controller_test.go b/plugin/kubernetes/controller_test.go index a6a94e06b..469eb59b6 100644 --- a/plugin/kubernetes/controller_test.go +++ b/plugin/kubernetes/controller_test.go @@ -6,6 +6,7 @@ import ( "strconv" "testing" + "github.com/coredns/coredns/plugin/kubernetes/object" "github.com/coredns/coredns/plugin/test" "github.com/miekg/dns" @@ -170,3 +171,68 @@ func createExternalSvc(suffix int, client kubernetes.Interface, ip net.IP) { }, }, meta.CreateOptions{}) } + +func TestServiceModified(t *testing.T) { + var tests = []struct { + oldSvc interface{} + newSvc interface{} + ichanged bool + echanged bool + }{ + { + oldSvc: nil, + newSvc: &object.Service{}, + ichanged: true, + echanged: false, + }, + { + oldSvc: &object.Service{}, + newSvc: nil, + ichanged: true, + echanged: false, + }, + { + oldSvc: nil, + newSvc: &object.Service{ExternalIPs: []string{"10.0.0.1"}}, + ichanged: true, + echanged: true, + }, + { + oldSvc: &object.Service{ExternalIPs: []string{"10.0.0.1"}}, + newSvc: nil, + ichanged: true, + echanged: true, + }, + { + oldSvc: &object.Service{ExternalIPs: []string{"10.0.0.1"}}, + newSvc: &object.Service{ExternalIPs: []string{"10.0.0.2"}}, + ichanged: false, + echanged: true, + }, + { + oldSvc: &object.Service{ExternalName: "10.0.0.1"}, + newSvc: &object.Service{ExternalName: "10.0.0.2"}, + ichanged: true, + echanged: false, + }, + { + oldSvc: &object.Service{Ports: []api.ServicePort{{Name: "test1"}}}, + newSvc: &object.Service{Ports: []api.ServicePort{{Name: "test2"}}}, + ichanged: true, + echanged: true, + }, + { + oldSvc: &object.Service{Ports: []api.ServicePort{{Name: "test1"}}}, + newSvc: &object.Service{Ports: []api.ServicePort{{Name: "test2"}, {Name: "test3"}}}, + ichanged: true, + echanged: true, + }, + } + + for i, test := range tests { + ichanged, echanged := serviceModified(test.oldSvc, test.newSvc) + if test.ichanged != ichanged || test.echanged != echanged { + t.Errorf("Expected %v, %v for test %v. Got %v, %v", test.ichanged, test.echanged, i, ichanged, echanged) + } + } +} diff --git a/plugin/kubernetes/external.go b/plugin/kubernetes/external.go index 77fa0ef58..74e7151fc 100644 --- a/plugin/kubernetes/external.go +++ b/plugin/kubernetes/external.go @@ -88,3 +88,26 @@ func (k *Kubernetes) ExternalAddress(state request.Request) []dns.RR { // plugin to bind to a different IP address. return k.nsAddrs(true, state.Zone) } + +// ExternalServices returns all services with external IPs +func (k *Kubernetes) ExternalServices(zone string) (services []msg.Service) { + zonePath := msg.Path(zone, coredns) + for _, svc := range k.APIConn.ServiceList() { + for _, ip := range svc.ExternalIPs { + for _, p := range svc.Ports { + s := msg.Service{Host: ip, Port: int(p.Port), TTL: k.ttl} + s.Key = strings.Join([]string{zonePath, svc.Namespace, svc.Name}, "/") + services = append(services, s) + s.Key = strings.Join(append([]string{zonePath, svc.Namespace, svc.Name}, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/") + s.TargetStrip = 2 + services = append(services, s) + } + } + } + return services +} + +//ExternalSerial returns the serial of the external zone +func (k *Kubernetes) ExternalSerial(string) uint32 { + return uint32(k.APIConn.Modified(true)) +} diff --git a/plugin/kubernetes/external_test.go b/plugin/kubernetes/external_test.go index 5b66579fc..28ccc608e 100644 --- a/plugin/kubernetes/external_test.go +++ b/plugin/kubernetes/external_test.go @@ -80,7 +80,7 @@ func (external) Run() func (external) Stop() error { return nil } func (external) EpIndexReverse(string) []*object.Endpoints { return nil } func (external) SvcIndexReverse(string) []*object.Service { return nil } -func (external) Modified() int64 { return 0 } +func (external) Modified(bool) int64 { return 0 } func (external) EpIndex(s string) []*object.Endpoints { return nil } func (external) EndpointsList() []*object.Endpoints { return nil } func (external) GetNodeByName(ctx context.Context, name string) (*api.Node, error) { return nil, nil } diff --git a/plugin/kubernetes/handler_test.go b/plugin/kubernetes/handler_test.go index 523341fda..d867f727a 100644 --- a/plugin/kubernetes/handler_test.go +++ b/plugin/kubernetes/handler_test.go @@ -541,7 +541,7 @@ func (APIConnServeTest) Run() {} func (APIConnServeTest) Stop() error { return nil } func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil } func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil } -func (APIConnServeTest) Modified() int64 { return int64(3) } +func (APIConnServeTest) Modified(bool) int64 { return int64(3) } func (APIConnServeTest) PodIndex(ip string) []*object.Pod { if ip != "10.240.0.1" { diff --git a/plugin/kubernetes/kubernetes.go b/plugin/kubernetes/kubernetes.go index 10bf29938..d8d50982c 100644 --- a/plugin/kubernetes/kubernetes.go +++ b/plugin/kubernetes/kubernetes.go @@ -588,7 +588,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. } // Serial return the SOA serial. -func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIConn.Modified()) } +func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIConn.Modified(false)) } // MinTTL returns the minimal TTL. func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl } diff --git a/plugin/kubernetes/kubernetes_test.go b/plugin/kubernetes/kubernetes_test.go index ca19043fb..9832fbd92 100644 --- a/plugin/kubernetes/kubernetes_test.go +++ b/plugin/kubernetes/kubernetes_test.go @@ -45,7 +45,7 @@ func (APIConnServiceTest) Stop() error { return ni func (APIConnServiceTest) PodIndex(string) []*object.Pod { return nil } func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil } func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil } -func (APIConnServiceTest) Modified() int64 { return 0 } +func (APIConnServiceTest) Modified(bool) int64 { return 0 } func (APIConnServiceTest) SvcIndex(string) []*object.Service { svcs := []*object.Service{ diff --git a/plugin/kubernetes/ns_test.go b/plugin/kubernetes/ns_test.go index c952d66f0..92ed3d4a8 100644 --- a/plugin/kubernetes/ns_test.go +++ b/plugin/kubernetes/ns_test.go @@ -21,7 +21,7 @@ func (APIConnTest) PodIndex(string) []*object.Pod { return nil } func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil } func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil } func (APIConnTest) EndpointsList() []*object.Endpoints { return nil } -func (APIConnTest) Modified() int64 { return 0 } +func (APIConnTest) Modified(bool) int64 { return 0 } func (a APIConnTest) SvcIndex(s string) []*object.Service { switch s { diff --git a/plugin/kubernetes/reverse_test.go b/plugin/kubernetes/reverse_test.go index 7ceee6590..aa21a9f86 100644 --- a/plugin/kubernetes/reverse_test.go +++ b/plugin/kubernetes/reverse_test.go @@ -22,7 +22,7 @@ func (APIConnReverseTest) PodIndex(string) []*object.Pod { return nil } func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil } func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil } func (APIConnReverseTest) ServiceList() []*object.Service { return nil } -func (APIConnReverseTest) Modified() int64 { return 0 } +func (APIConnReverseTest) Modified(bool) int64 { return 0 } func (APIConnReverseTest) SvcIndex(svc string) []*object.Service { if svc != "svc1.testns" {