middleware/kubernetes: Implement current federation beta (#723)
* federation initial commit * UTs/bugfixes * federation bits * polish, cover UT gaps * add TODO * go fmt & todo note * remove unrelated change * pr changes * start node watcher * get real node name * remove unused case
This commit is contained in:
parent
8e86fa6f23
commit
930c54ef62
9 changed files with 420 additions and 17 deletions
|
@ -115,6 +115,12 @@ kubernetes coredns.local {
|
||||||
# a path to a file structured like resolv.conf.
|
# a path to a file structured like resolv.conf.
|
||||||
upstream 12.34.56.78:53
|
upstream 12.34.56.78:53
|
||||||
|
|
||||||
|
# federation <federation-name> <federation-domain>
|
||||||
|
#
|
||||||
|
# Defines federation membership. One line for each federation membership.
|
||||||
|
# Each line consists of the name of the federation, and the domain.
|
||||||
|
federation myfed foo.example.com
|
||||||
|
|
||||||
# fallthrough
|
# fallthrough
|
||||||
#
|
#
|
||||||
# If a query for a record in the cluster zone results in NXDOMAIN,
|
# If a query for a record in the cluster zone results in NXDOMAIN,
|
||||||
|
|
|
@ -39,6 +39,9 @@ type dnsController interface {
|
||||||
ServiceList() []*api.Service
|
ServiceList() []*api.Service
|
||||||
PodIndex(string) []interface{}
|
PodIndex(string) []interface{}
|
||||||
EndpointsList() api.EndpointsList
|
EndpointsList() api.EndpointsList
|
||||||
|
|
||||||
|
GetNodeByName(string) (api.Node, error)
|
||||||
|
|
||||||
Run()
|
Run()
|
||||||
Stop() error
|
Stop() error
|
||||||
}
|
}
|
||||||
|
@ -52,6 +55,7 @@ type dnsControl struct {
|
||||||
podController *cache.Controller
|
podController *cache.Controller
|
||||||
nsController *cache.Controller
|
nsController *cache.Controller
|
||||||
epController *cache.Controller
|
epController *cache.Controller
|
||||||
|
nodeController *cache.Controller
|
||||||
|
|
||||||
svcLister cache.StoreToServiceLister
|
svcLister cache.StoreToServiceLister
|
||||||
podLister cache.StoreToPodLister
|
podLister cache.StoreToPodLister
|
||||||
|
@ -66,8 +70,12 @@ type dnsControl struct {
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dnsControlOpts struct {
|
||||||
|
initPodCache bool
|
||||||
|
}
|
||||||
|
|
||||||
// newDNSController creates a controller for CoreDNS.
|
// newDNSController creates a controller for CoreDNS.
|
||||||
func newdnsController(kubeClient *kubernetes.Clientset, resyncPeriod time.Duration, lselector *labels.Selector, initPodCache bool) *dnsControl {
|
func newdnsController(kubeClient *kubernetes.Clientset, resyncPeriod time.Duration, lselector *labels.Selector, opts dnsControlOpts) *dnsControl {
|
||||||
dns := dnsControl{
|
dns := dnsControl{
|
||||||
client: kubeClient,
|
client: kubeClient,
|
||||||
selector: lselector,
|
selector: lselector,
|
||||||
|
@ -84,7 +92,7 @@ func newdnsController(kubeClient *kubernetes.Clientset, resyncPeriod time.Durati
|
||||||
cache.ResourceEventHandlerFuncs{},
|
cache.ResourceEventHandlerFuncs{},
|
||||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||||
|
|
||||||
if initPodCache {
|
if opts.initPodCache {
|
||||||
dns.podLister.Indexer, dns.podController = cache.NewIndexerInformer(
|
dns.podLister.Indexer, dns.podController = cache.NewIndexerInformer(
|
||||||
&cache.ListWatch{
|
&cache.ListWatch{
|
||||||
ListFunc: podListFunc(dns.client, namespace, dns.selector),
|
ListFunc: podListFunc(dns.client, namespace, dns.selector),
|
||||||
|
@ -368,3 +376,16 @@ func (dns *dnsControl) EndpointsList() api.EndpointsList {
|
||||||
|
|
||||||
return epl
|
return epl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dns *dnsControl) GetNodeByName(name string) (api.Node, error) {
|
||||||
|
v1node, err := dns.client.Core().Nodes().Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return api.Node{}, err
|
||||||
|
}
|
||||||
|
var apinode api.Node
|
||||||
|
err = v1.Convert_v1_Node_To_api_Node(v1node, &apinode, nil)
|
||||||
|
if err != nil {
|
||||||
|
return api.Node{}, err
|
||||||
|
}
|
||||||
|
return apinode, nil
|
||||||
|
}
|
||||||
|
|
102
middleware/kubernetes/federation.go
Normal file
102
middleware/kubernetes/federation.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/middleware/etcd/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Federation struct {
|
||||||
|
name string
|
||||||
|
zone string
|
||||||
|
}
|
||||||
|
|
||||||
|
var localNodeName string
|
||||||
|
var federationZone string
|
||||||
|
var federationRegion string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TODO: Do not hardcode these labels. Pull them out of the API instead.
|
||||||
|
//
|
||||||
|
// We can get them via ....
|
||||||
|
// import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
// metav1.LabelZoneFailureDomain
|
||||||
|
// metav1.LabelZoneRegion
|
||||||
|
//
|
||||||
|
// But importing above breaks coredns with flag collision of 'log_dir'
|
||||||
|
|
||||||
|
LabelAvailabilityZone = "failure-domain.beta.kubernetes.io/zone"
|
||||||
|
LabelRegion = "failure-domain.beta.kubernetes.io/region"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stripFederation removes the federation segment from the segment list, if it
|
||||||
|
// matches a configured federation name.
|
||||||
|
func (k *Kubernetes) stripFederation(segs []string) (string, []string) {
|
||||||
|
|
||||||
|
if len(segs) < 3 {
|
||||||
|
return "", segs
|
||||||
|
}
|
||||||
|
for _, f := range k.Federations {
|
||||||
|
if f.name == segs[len(segs)-2] {
|
||||||
|
fed := segs[len(segs)-2]
|
||||||
|
segs[len(segs)-2] = segs[len(segs)-1]
|
||||||
|
segs = segs[:len(segs)-1]
|
||||||
|
return fed, segs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", segs
|
||||||
|
}
|
||||||
|
|
||||||
|
// federationCNAMERecord returns a service record for the requested federated service
|
||||||
|
// with the target host in the federated CNAME format which the external DNS provider
|
||||||
|
// should be able to resolve
|
||||||
|
func (k *Kubernetes) federationCNAMERecord(r recordRequest) msg.Service {
|
||||||
|
|
||||||
|
myNodeName := k.localNodeName()
|
||||||
|
node, err := k.APIConn.GetNodeByName(myNodeName)
|
||||||
|
if err != nil {
|
||||||
|
return msg.Service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range k.Federations {
|
||||||
|
if f.name != r.federation {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.endpoint == "" {
|
||||||
|
return msg.Service{
|
||||||
|
Key: strings.Join([]string{msg.Path(r.zone, "coredns"), r.typeName, r.federation, r.namespace, r.service}, "/"),
|
||||||
|
Host: strings.Join([]string{r.service, r.namespace, r.federation, r.typeName, node.Labels[LabelAvailabilityZone], node.Labels[LabelRegion], f.zone}, "."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msg.Service{
|
||||||
|
Key: strings.Join([]string{msg.Path(r.zone, "coredns"), r.typeName, r.federation, r.namespace, r.service, r.endpoint}, "/"),
|
||||||
|
Host: strings.Join([]string{r.endpoint, r.service, r.namespace, r.federation, r.typeName, node.Labels[LabelAvailabilityZone], node.Labels[LabelRegion], f.zone}, "."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg.Service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Kubernetes) localNodeName() string {
|
||||||
|
if localNodeName != "" {
|
||||||
|
return localNodeName
|
||||||
|
}
|
||||||
|
localIP := k.localPodIP()
|
||||||
|
if localIP == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Find endpoint matching localIP
|
||||||
|
endpointsList := k.APIConn.EndpointsList()
|
||||||
|
for _, ep := range endpointsList.Items {
|
||||||
|
for _, eps := range ep.Subsets {
|
||||||
|
for _, addr := range eps.Addresses {
|
||||||
|
if localIP.Equal(net.ParseIP(addr.IP)) {
|
||||||
|
localNodeName = *addr.NodeName
|
||||||
|
return localNodeName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
104
middleware/kubernetes/federation_test.go
Normal file
104
middleware/kubernetes/federation_test.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/middleware/etcd/msg"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"k8s.io/client-go/1.5/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testStripFederation(t *testing.T, k Kubernetes, input []string, expectedFed string, expectedSegs string) {
|
||||||
|
fed, segs := k.stripFederation(input)
|
||||||
|
|
||||||
|
if expectedSegs != strings.Join(segs, ".") {
|
||||||
|
t.Errorf("For '%v', expected segs result '%v'. Instead got result '%v'.", strings.Join(input, "."), expectedSegs, strings.Join(segs, "."))
|
||||||
|
}
|
||||||
|
if expectedFed != fed {
|
||||||
|
t.Errorf("For '%v', expected fed result '%v'. Instead got result '%v'.", strings.Join(input, "."), expectedFed, fed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStripFederation(t *testing.T) {
|
||||||
|
k := Kubernetes{Zones: []string{"inter.webs.test"}}
|
||||||
|
k.Federations = []Federation{{name: "fed", zone: "era.tion.com"}}
|
||||||
|
|
||||||
|
testStripFederation(t, k, []string{"service", "ns", "fed", "svc"}, "fed", "service.ns.svc")
|
||||||
|
testStripFederation(t, k, []string{"service", "ns", "foo", "svc"}, "", "service.ns.foo.svc")
|
||||||
|
testStripFederation(t, k, []string{"foo", "bar"}, "", "foo.bar")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiConnFedTest struct{}
|
||||||
|
|
||||||
|
func (apiConnFedTest) Run() { return }
|
||||||
|
func (apiConnFedTest) Stop() error { return nil }
|
||||||
|
func (apiConnFedTest) ServiceList() []*api.Service { return []*api.Service{} }
|
||||||
|
func (apiConnFedTest) PodIndex(string) []interface{} { return nil }
|
||||||
|
|
||||||
|
func (apiConnFedTest) EndpointsList() api.EndpointsList {
|
||||||
|
n := "test.node.foo.bar"
|
||||||
|
return api.EndpointsList{
|
||||||
|
Items: []api.Endpoints{
|
||||||
|
{
|
||||||
|
Subsets: []api.EndpointSubset{
|
||||||
|
{
|
||||||
|
Addresses: []api.EndpointAddress{
|
||||||
|
{
|
||||||
|
IP: "10.9.8.7",
|
||||||
|
NodeName: &n,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (apiConnFedTest) GetNodeByName(name string) (api.Node, error) {
|
||||||
|
if name != "test.node.foo.bar" {
|
||||||
|
return api.Node{}, nil
|
||||||
|
}
|
||||||
|
return api.Node{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "test.node.foo.bar",
|
||||||
|
Labels: map[string]string{
|
||||||
|
LabelRegion: "fd-r",
|
||||||
|
LabelAvailabilityZone: "fd-az",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFederationCNAMERecord(t *testing.T, k Kubernetes, input recordRequest, expected msg.Service) {
|
||||||
|
svc := k.federationCNAMERecord(input)
|
||||||
|
|
||||||
|
if expected.Host != svc.Host {
|
||||||
|
t.Errorf("For '%v', expected Host result '%v'. Instead got result '%v'.", input, expected.Host, svc.Host)
|
||||||
|
}
|
||||||
|
if expected.Key != svc.Key {
|
||||||
|
t.Errorf("For '%v', expected Key result '%v'. Instead got result '%v'.", input, expected.Key, svc.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFederationCNAMERecord(t *testing.T) {
|
||||||
|
k := Kubernetes{Zones: []string{"inter.webs"}}
|
||||||
|
k.Federations = []Federation{{name: "fed", zone: "era.tion.com"}}
|
||||||
|
k.APIConn = apiConnFedTest{}
|
||||||
|
|
||||||
|
var r recordRequest
|
||||||
|
|
||||||
|
r, _ = k.parseRequest("s1.ns.fed.svc.inter.webs", dns.TypeA)
|
||||||
|
localPodIP = net.ParseIP("10.9.8.7")
|
||||||
|
testFederationCNAMERecord(t, k, r, msg.Service{Key: "/coredns/webs/inter/svc/fed/ns/s1", Host: "s1.ns.fed.svc.fd-az.fd-r.era.tion.com"})
|
||||||
|
|
||||||
|
r, _ = k.parseRequest("ep1.s1.ns.fed.svc.inter.webs", dns.TypeA)
|
||||||
|
testFederationCNAMERecord(t, k, r, msg.Service{Key: "/coredns/webs/inter/svc/fed/ns/s1/ep1", Host: "ep1.s1.ns.fed.svc.fd-az.fd-r.era.tion.com"})
|
||||||
|
|
||||||
|
r, _ = k.parseRequest("ep1.s1.ns.foo.svc.inter.webs", dns.TypeA)
|
||||||
|
testFederationCNAMERecord(t, k, r, msg.Service{Key: "", Host: ""})
|
||||||
|
|
||||||
|
}
|
|
@ -39,6 +39,7 @@ type Kubernetes struct {
|
||||||
APIConn dnsController
|
APIConn dnsController
|
||||||
ResyncPeriod time.Duration
|
ResyncPeriod time.Duration
|
||||||
Namespaces []string
|
Namespaces []string
|
||||||
|
Federations []Federation
|
||||||
LabelSelector *unversionedapi.LabelSelector
|
LabelSelector *unversionedapi.LabelSelector
|
||||||
Selector *labels.Selector
|
Selector *labels.Selector
|
||||||
PodMode string
|
PodMode string
|
||||||
|
@ -78,9 +79,18 @@ type pod struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type recordRequest struct {
|
type recordRequest struct {
|
||||||
port, protocol, endpoint, service, namespace, typeName, zone string
|
port string
|
||||||
|
protocol string
|
||||||
|
endpoint string
|
||||||
|
service string
|
||||||
|
namespace string
|
||||||
|
typeName string
|
||||||
|
zone string
|
||||||
|
federation string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localPodIP net.IP
|
||||||
|
|
||||||
var errNoItems = errors.New("no items found")
|
var errNoItems = errors.New("no items found")
|
||||||
var errNsNotExposed = errors.New("namespace is not exposed")
|
var errNsNotExposed = errors.New("namespace is not exposed")
|
||||||
var errInvalidRequest = errors.New("invalid query name")
|
var errInvalidRequest = errors.New("invalid query name")
|
||||||
|
@ -236,16 +246,20 @@ func (k *Kubernetes) InitKubeCache() (err error) {
|
||||||
log.Printf("[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed.", unversionedapi.FormatLabelSelector(k.LabelSelector))
|
log.Printf("[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed.", unversionedapi.FormatLabelSelector(k.LabelSelector))
|
||||||
}
|
}
|
||||||
|
|
||||||
k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector, k.PodMode == PodModeVerified)
|
opts := dnsControlOpts{
|
||||||
|
initPodCache: k.PodMode == PodModeVerified,
|
||||||
|
}
|
||||||
|
k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector, opts)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubernetes) parseRequest(lowerCasedName string, qtype uint16) (r recordRequest, err error) {
|
func (k *Kubernetes) parseRequest(lowerCasedName string, qtype uint16) (r recordRequest, err error) {
|
||||||
// 3 Possible cases
|
// 3 Possible cases
|
||||||
// SRV Request: _port._protocol.service.namespace.type.zone
|
// SRV Request: _port._protocol.service.namespace.[federation.]type.zone
|
||||||
// A Request (endpoint): endpoint.service.namespace.type.zone
|
// A Request (endpoint): endpoint.service.namespace.[federation.]type.zone
|
||||||
// A Request (service): service.namespace.type.zone
|
// A Request (service): service.namespace.[federation.]type.zone
|
||||||
|
|
||||||
// separate zone from rest of lowerCasedName
|
// separate zone from rest of lowerCasedName
|
||||||
var segs []string
|
var segs []string
|
||||||
for _, z := range k.Zones {
|
for _, z := range k.Zones {
|
||||||
|
@ -261,6 +275,8 @@ func (k *Kubernetes) parseRequest(lowerCasedName string, qtype uint16) (r record
|
||||||
return r, errZoneNotFound
|
return r, errZoneNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.federation, segs = k.stripFederation(segs)
|
||||||
|
|
||||||
if qtype == dns.TypeNS {
|
if qtype == dns.TypeNS {
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
@ -339,11 +355,17 @@ func (k *Kubernetes) Records(r recordRequest) ([]msg.Service, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(services) == 0 && len(pods) == 0 {
|
if len(services) == 0 && len(pods) == 0 {
|
||||||
// Did not find item in k8s
|
// Did not find item in k8s, try federated
|
||||||
|
if r.federation != "" {
|
||||||
|
fedCNAME := k.federationCNAMERecord(r)
|
||||||
|
if fedCNAME.Key != "" {
|
||||||
|
return []msg.Service{fedCNAME}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil, errNoItems
|
return nil, errNoItems
|
||||||
}
|
}
|
||||||
|
|
||||||
records := k.getRecordsForK8sItems(services, pods, r.zone)
|
records := k.getRecordsForK8sItems(services, pods, r)
|
||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,27 +382,37 @@ func endpointHostname(addr api.EndpointAddress) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone string) (records []msg.Service) {
|
func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, r recordRequest) (records []msg.Service) {
|
||||||
zonePath := msg.Path(zone, "coredns")
|
zonePath := msg.Path(r.zone, "coredns")
|
||||||
|
|
||||||
for _, svc := range services {
|
for _, svc := range services {
|
||||||
if svc.addr == api.ClusterIPNone {
|
if svc.addr == api.ClusterIPNone {
|
||||||
// This is a headless service, create records for each endpoint
|
// This is a headless service, create records for each endpoint
|
||||||
for _, ep := range svc.endpoints {
|
for _, ep := range svc.endpoints {
|
||||||
s := msg.Service{
|
s := msg.Service{
|
||||||
Key: strings.Join([]string{zonePath, "svc", svc.namespace, svc.name, endpointHostname(ep.addr)}, "/"),
|
|
||||||
Host: ep.addr.IP,
|
Host: ep.addr.IP,
|
||||||
Port: int(ep.port.Port),
|
Port: int(ep.port.Port),
|
||||||
}
|
}
|
||||||
|
if r.federation != "" {
|
||||||
|
s.Key = strings.Join([]string{zonePath, "svc", r.federation, svc.namespace, svc.name, endpointHostname(ep.addr)}, "/")
|
||||||
|
} else {
|
||||||
|
s.Key = strings.Join([]string{zonePath, "svc", svc.namespace, svc.name, endpointHostname(ep.addr)}, "/")
|
||||||
|
}
|
||||||
records = append(records, s)
|
records = append(records, s)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create records for each exposed port...
|
// Create records for each exposed port...
|
||||||
for _, p := range svc.ports {
|
for _, p := range svc.ports {
|
||||||
s := msg.Service{
|
s := msg.Service{
|
||||||
Key: strings.Join([]string{zonePath, "svc", svc.namespace, svc.name}, "/"),
|
|
||||||
Host: svc.addr,
|
Host: svc.addr,
|
||||||
Port: int(p.Port)}
|
Port: int(p.Port)}
|
||||||
|
|
||||||
|
if r.federation != "" {
|
||||||
|
s.Key = strings.Join([]string{zonePath, "svc", r.federation, svc.namespace, svc.name}, "/")
|
||||||
|
} else {
|
||||||
|
s.Key = strings.Join([]string{zonePath, "svc", svc.namespace, svc.name}, "/")
|
||||||
|
}
|
||||||
|
|
||||||
records = append(records, s)
|
records = append(records, s)
|
||||||
}
|
}
|
||||||
// If the addr is not an IP (i.e. an external service), add the record ...
|
// If the addr is not an IP (i.e. an external service), add the record ...
|
||||||
|
@ -388,6 +420,11 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone
|
||||||
Key: strings.Join([]string{zonePath, "svc", svc.namespace, svc.name}, "/"),
|
Key: strings.Join([]string{zonePath, "svc", svc.namespace, svc.name}, "/"),
|
||||||
Host: svc.addr}
|
Host: svc.addr}
|
||||||
if t, _ := s.HostType(); t == dns.TypeCNAME {
|
if t, _ := s.HostType(); t == dns.TypeCNAME {
|
||||||
|
if r.federation != "" {
|
||||||
|
s.Key = strings.Join([]string{zonePath, "svc", r.federation, svc.namespace, svc.name}, "/")
|
||||||
|
} else {
|
||||||
|
s.Key = strings.Join([]string{zonePath, "svc", svc.namespace, svc.name}, "/")
|
||||||
|
}
|
||||||
records = append(records, s)
|
records = append(records, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,3 +612,21 @@ func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service {
|
||||||
func symbolContainsWildcard(symbol string) bool {
|
func symbolContainsWildcard(symbol string) bool {
|
||||||
return (symbol == "*" || symbol == "any")
|
return (symbol == "*" || symbol == "any")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *Kubernetes) localPodIP() net.IP {
|
||||||
|
if localPodIP != nil {
|
||||||
|
return localPodIP
|
||||||
|
}
|
||||||
|
addrs, _ := k.interfaceAddrs.interfaceAddrs()
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ip, _, _ := net.ParseCIDR(addr.String())
|
||||||
|
ip = ip.To4()
|
||||||
|
if ip == nil || ip.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
localPodIP = ip
|
||||||
|
return localPodIP
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -339,6 +339,8 @@ func (APIConnServiceTest) PodIndex(string) []interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (APIConnServiceTest) EndpointsList() api.EndpointsList {
|
func (APIConnServiceTest) EndpointsList() api.EndpointsList {
|
||||||
|
n := "test.node.foo.bar"
|
||||||
|
|
||||||
return api.EndpointsList{
|
return api.EndpointsList{
|
||||||
Items: []api.Endpoints{
|
Items: []api.Endpoints{
|
||||||
{
|
{
|
||||||
|
@ -407,13 +409,37 @@ func (APIConnServiceTest) EndpointsList() api.EndpointsList {
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Subsets: []api.EndpointSubset{
|
||||||
|
{
|
||||||
|
Addresses: []api.EndpointAddress{
|
||||||
|
{
|
||||||
|
IP: "10.9.8.7",
|
||||||
|
NodeName: &n,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (APIConnServiceTest) GetNodeByName(name string) (api.Node, error) {
|
||||||
|
return api.Node{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "test.node.foo.bar",
|
||||||
|
Labels: map[string]string{
|
||||||
|
LabelRegion: "fd-r",
|
||||||
|
LabelAvailabilityZone: "fd-az",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
func TestServices(t *testing.T) {
|
func TestServices(t *testing.T) {
|
||||||
|
|
||||||
k := Kubernetes{Zones: []string{"interwebs.test"}}
|
k := Kubernetes{Zones: []string{"interwebs.test"}}
|
||||||
|
k.Federations = []Federation{{name: "fed", zone: "era.tion.com"}}
|
||||||
k.APIConn = &APIConnServiceTest{}
|
k.APIConn = &APIConnServiceTest{}
|
||||||
|
|
||||||
type svcAns struct {
|
type svcAns struct {
|
||||||
|
@ -432,6 +458,10 @@ func TestServices(t *testing.T) {
|
||||||
|
|
||||||
// External Services
|
// External Services
|
||||||
{qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: svcAns{host: "coredns.io", key: "/coredns/test/interwebs/svc/testns/external"}},
|
{qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: svcAns{host: "coredns.io", key: "/coredns/test/interwebs/svc/testns/external"}},
|
||||||
|
|
||||||
|
// Federated Services
|
||||||
|
{qname: "svc1.testns.fed.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "10.0.0.1", key: "/coredns/test/interwebs/svc/fed/testns/svc1"}},
|
||||||
|
{qname: "svc0.testns.fed.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "svc0.testns.fed.svc.fd-az.fd-r.era.tion.com", key: "/coredns/test/interwebs/svc/fed/testns/svc0"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
|
@ -104,6 +104,8 @@ func (APIConnTest) EndpointsList() api.EndpointsList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (APIConnTest) GetNodeByName(name string) (api.Node, error) { return api.Node{}, nil }
|
||||||
|
|
||||||
type interfaceAddrsTest struct{}
|
type interfaceAddrsTest struct{}
|
||||||
|
|
||||||
func (i interfaceAddrsTest) interfaceAddrs() ([]net.Addr, error) {
|
func (i interfaceAddrsTest) interfaceAddrs() ([]net.Addr, error) {
|
||||||
|
|
|
@ -177,6 +177,19 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
k8s.Proxy = proxy.NewLookup(ups)
|
k8s.Proxy = proxy.NewLookup(ups)
|
||||||
|
case "federation": // name zone
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
if len(args) == 2 {
|
||||||
|
k8s.Federations = append(k8s.Federations, Federation{
|
||||||
|
name: args[0],
|
||||||
|
zone: args[1],
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Incorrect number of arguments for federation. Got %v, expect 2.", len(args))
|
||||||
|
}
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return k8s, nil
|
return k8s, nil
|
||||||
|
|
|
@ -29,6 +29,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
expectedCidrs []net.IPNet
|
expectedCidrs []net.IPNet
|
||||||
expectedFallthrough bool
|
expectedFallthrough bool
|
||||||
expectedUpstreams []string
|
expectedUpstreams []string
|
||||||
|
expectedFederations []Federation
|
||||||
}{
|
}{
|
||||||
// positive
|
// positive
|
||||||
{
|
{
|
||||||
|
@ -44,6 +45,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with multiple zones",
|
"kubernetes keyword with multiple zones",
|
||||||
|
@ -58,6 +60,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with zone and empty braces",
|
"kubernetes keyword with zone and empty braces",
|
||||||
|
@ -73,6 +76,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword with url",
|
"endpoint keyword with url",
|
||||||
|
@ -89,6 +93,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with one namespace",
|
"namespaces keyword with one namespace",
|
||||||
|
@ -105,6 +110,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with multiple namespaces",
|
"namespaces keyword with multiple namespaces",
|
||||||
|
@ -121,6 +127,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period in seconds",
|
"resync period in seconds",
|
||||||
|
@ -137,6 +144,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period in minutes",
|
"resync period in minutes",
|
||||||
|
@ -153,6 +161,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"basic label selector",
|
"basic label selector",
|
||||||
|
@ -169,6 +178,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"multi-label selector",
|
"multi-label selector",
|
||||||
|
@ -185,6 +195,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fully specified valid config",
|
"fully specified valid config",
|
||||||
|
@ -205,6 +216,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// negative
|
// negative
|
||||||
{
|
{
|
||||||
|
@ -220,6 +232,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword without a zone",
|
"kubernetes keyword without a zone",
|
||||||
|
@ -234,6 +247,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword without an endpoint value",
|
"endpoint keyword without an endpoint value",
|
||||||
|
@ -250,6 +264,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespace keyword without a namespace value",
|
"namespace keyword without a namespace value",
|
||||||
|
@ -266,6 +281,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resyncperiod keyword without a duration value",
|
"resyncperiod keyword without a duration value",
|
||||||
|
@ -282,6 +298,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period no units",
|
"resync period no units",
|
||||||
|
@ -298,6 +315,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period invalid",
|
"resync period invalid",
|
||||||
|
@ -314,6 +332,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labels with no selector value",
|
"labels with no selector value",
|
||||||
|
@ -330,6 +349,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labels with invalid selector value",
|
"labels with invalid selector value",
|
||||||
|
@ -346,6 +366,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// pods disabled
|
// pods disabled
|
||||||
{
|
{
|
||||||
|
@ -363,6 +384,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// pods insecure
|
// pods insecure
|
||||||
{
|
{
|
||||||
|
@ -380,6 +402,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// pods verified
|
// pods verified
|
||||||
{
|
{
|
||||||
|
@ -397,6 +420,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// pods invalid
|
// pods invalid
|
||||||
{
|
{
|
||||||
|
@ -414,6 +438,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// cidrs ok
|
// cidrs ok
|
||||||
{
|
{
|
||||||
|
@ -431,6 +456,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
[]net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")},
|
[]net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")},
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// cidrs ok
|
// cidrs ok
|
||||||
{
|
{
|
||||||
|
@ -448,6 +474,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// fallthrough invalid
|
// fallthrough invalid
|
||||||
{
|
{
|
||||||
|
@ -465,6 +492,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// Valid upstream
|
// Valid upstream
|
||||||
{
|
{
|
||||||
|
@ -482,6 +510,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
[]string{"13.14.15.16:53"},
|
[]string{"13.14.15.16:53"},
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
// Invalid upstream
|
// Invalid upstream
|
||||||
{
|
{
|
||||||
|
@ -499,6 +528,47 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
|
[]Federation{},
|
||||||
|
},
|
||||||
|
// Valid federations
|
||||||
|
{
|
||||||
|
"valid upstream",
|
||||||
|
`kubernetes coredns.local {
|
||||||
|
federation foo bar.crawl.com
|
||||||
|
federation fed era.tion.com
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
|
"",
|
||||||
|
defaultPodMode,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
[]Federation{
|
||||||
|
{name: "foo", zone: "bar.crawl.com"},
|
||||||
|
{name: "fed", zone: "era.tion.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Invalid federations
|
||||||
|
{
|
||||||
|
"valid upstream",
|
||||||
|
`kubernetes coredns.local {
|
||||||
|
federation starship
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
`Incorrect number of arguments for federation. Got 1, expect 2.`,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
|
"",
|
||||||
|
defaultPodMode,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
[]Federation{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue