diff options
Diffstat (limited to 'middleware/kubernetes')
25 files changed, 0 insertions, 3445 deletions
diff --git a/middleware/kubernetes/DEV-README.md b/middleware/kubernetes/DEV-README.md deleted file mode 100644 index 4f652b578..000000000 --- a/middleware/kubernetes/DEV-README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Basic Setup for Development and Testing - -## Launch Kubernetes - -To run the tests, you'll need a private, live Kubernetes cluster. If you don't have one, -you can try out [minikube](https://github.com/kubernetes/minikube), which is -also available via Homebrew for OS X users. - -## Configure Test Data - -The test data is all in [this manifest](https://github.com/coredns/coredns/blob/master/.travis/kubernetes/dns-test.yaml) -and you can load it with `kubectl apply -f`. It will create a couple namespaces and some services. -For the tests to pass, you should not create anything else in the cluster. - -## Proxy the API Server - -Assuming your Kuberentes API server isn't running on http://localhost:8080, you will need to proxy from that -port to your cluster. You can do this with `kubectl proxy --port 8080`. - -## Run CoreDNS Kubernetes Tests - -Now you can run the tests locally, for example: - -~~~ -$ cd $GOPATH/src/github.com/coredns/coredns/test -$ go test -v -tags k8s -~~~ - -# Implementation Notes/Ideas - -* Additional features: - * Implement IP selection and ordering (internal/external). Related to - wildcards and SkyDNS use of CNAMES. - * Expose arbitrary kubernetes repository data as TXT records? -* DNS Correctness - * Do we need to generate synthetic zone records for namespaces? - * Do we need to generate synthetic zone records for the skydns synthetic zones? -* Test cases - * Test with CoreDNS caching. CoreDNS caching for DNS response is working - using the `cache` directive. Tested working using 20s cache timeout - and A-record queries. Automate testing with cache in place. - * Automate CoreDNS performance tests. Initially for zone files, and for - pre-loaded k8s API cache. With and without CoreDNS response caching. diff --git a/middleware/kubernetes/README.md b/middleware/kubernetes/README.md deleted file mode 100644 index 3e199c93a..000000000 --- a/middleware/kubernetes/README.md +++ /dev/null @@ -1,167 +0,0 @@ -# kubernetes - -The *kubernetes* middleware enables the reading zone data from a Kubernetes cluster. It implements -the [Kubernetes DNS-Based Service Discovery -Specification](https://github.com/kubernetes/dns/blob/master/docs/specification.md). - -CoreDNS running the kubernetes middleware can be used as a replacement of kube-dns in a kubernetes -cluster. See the [deployment](https://github.com/coredns/deployment) repository for details on [how -to deploy CoreDNS in Kubernetes](https://github.com/coredns/deployment/tree/master/kubernetes). - -[stubDomains](http://blog.kubernetes.io/2017/04/configuring-private-dns-zones-upstream-nameservers-kubernetes.html) -are implemented via the *proxy* middleware. - -## Syntax - -~~~ -kubernetes [ZONES...] -~~~ - -With only the directive specified, the *kubernetes* middleware will default to the zone specified in -the server's block. It will handle all queries in that zone and connect to Kubernetes in-cluster. It -will not provide PTR records for services, or A records for pods. If **ZONES** is used it specifies -all the zones the middleware should be authoritative for. - -``` -kubernetes [ZONES...] { - resyncperiod DURATION - endpoint URL - tls CERT KEY CACERT - namespaces NAMESPACE... - labels EXPRESSION - pods POD-MODE - upstream ADDRESS... - ttl TTL - fallthrough -} -``` -* `resyncperiod` specifies the Kubernetes data API **DURATION** period. -* `endpoint` specifies the **URL** for a remove k8s API endpoint. - If omitted, it will connect to k8s in-cluster using the cluster service account. - Multiple k8s API endpoints could be specified, separated by `,`s, e.g. - `endpoint http://k8s-endpoint1:8080,http://k8s-endpoint2:8080`. CoreDNS - will automatically perform a healthcheck and proxy to the healthy k8s API endpoint. -* `tls` **CERT** **KEY** **CACERT** are the TLS cert, key and the CA cert file names for remote k8s connection. - This option is ignored if connecting in-cluster (i.e. endpoint is not specified). -* `namespaces` **NAMESPACE [NAMESPACE...]**, exposed only the k8s namespaces listed. - If this option is omitted all namespaces are exposed -* `labels` **EXPRESSION** only exposes the records for Kubernetes objects that match this label selector. - The label selector syntax is described in the - [Kubernetes User Guide - Labels](http://kubernetes.io/docs/user-guide/labels/). An example that - only exposes objects labeled as "application=nginx" in the "staging" or "qa" environments, would - use: `labels environment in (staging, qa),application=nginx`. -* `pods` **POD-MODE** sets the mode for handling IP-based pod A records, e.g. - `1-2-3-4.ns.pod.cluster.local. in A 1.2.3.4`. - This option is provided to facilitate use of SSL certs when connecting directly to pods. Valid - values for **POD-MODE**: - - * `disabled`: Default. Do not process pod requests, always returning `NXDOMAIN` - * `insecure`: Always return an A record with IP from request (without checking k8s). This option - is is vulnerable to abuse if used maliciously in conjunction with wildcard SSL certs. This - option is provided for backward compatibility with kube-dns. - * `verified`: Return an A record if there exists a pod in same namespace with matching IP. This - option requires substantially more memory than in insecure mode, since it will maintain a watch - on all pods. - -* `upstream` **ADDRESS [ADDRESS...]** defines the upstream resolvers used for resolving services - that point to external hosts (External Services). **ADDRESS** can be an ip, an ip:port, or a path - to a file structured like resolv.conf. -* `ttl` allows you to set a custom TTL for responses. The default (and allowed minimum) is to use - 5 seconds, the maximum is capped at 3600 seconds. -* `fallthrough` If a query for a record in the cluster zone results in NXDOMAIN, normally that is - what the response will be. However, if you specify this option, the query will instead be passed - on down the middleware chain, which can include another middleware to handle the query. - -## Examples - -Handle all queries in the `cluster.local` zone. Connect to Kubernetes in-cluster. -Also handle all `PTR` requests for `10.0.0.0/16` . Verify the existence of pods when answering pod -requests. Resolve upstream records against `10.102.3.10`. Note we show the entire server block -here: - -~~~ txt -10.0.0.0/16 cluster.local { - kubernetes { - pods verified - upstream 10.102.3.10:53 - } -} -~~~ - -Or you can selectively expose some namespaces: - -~~~ txt -kubernetes cluster.local { - namespaces test staging -} -~~~ - -Connect to Kubernetes with CoreDNS running outside the cluster: - -~~~ txt -kubernetes cluster.local { - endpoint https://k8s-endpoint:8443 - tls cert key cacert -} -~~~ - -Here we use the *proxy* middleware to implement stubDomains that forwards `example.org` and -`example.com` to another nameserver. - -~~~ txt -cluster.local { - kubernetes { - endpoint https://k8s-endpoint:8443 - tls cert key cacert - } -} -example.org { - proxy . 8.8.8.8:53 -} -example.com { - proxy . 8.8.8.8:53 -} -~~~ - -## AutoPath - -The *kubernetes* middleware can be used in conjunction with the *autopath* middleware. Using this -feature enables server-side domain search path completion in kubernetes clusters. Note: `pods` must -be set to `verified` for this to function properly. - - cluster.local { - autopath @kubernetes - kubernetes { - pods verified - } - } - -## Federation - -The *kubernetes* middleware can be used in conjunction with the *federation* middleware. Using this -feature enables serving federated domains from the kubernetes clusters. - - cluster.local { - federation { - fallthrough - prod prod.example.org - staging staging.example.org - - } - kubernetes - } - - -## Wildcards - -Some query labels accept a wildcard value to match any value. If a label is a valid wildcard (\*, -or the word "any"), then that label will match all values. The labels that accept wildcards are: - - * _service_ in an `A` record request: _service_.namespace.svc.zone. - * e.g. `*.ns.svc.myzone.local` - * _namespace_ in an `A` record request: service._namespace_.svc.zone. - * e.g. `nginx.*.svc.myzone.local` - * _port and/or protocol_ in an `SRV` request: __port_.__protocol_.service.namespace.svc.zone. - * e.g. `_http.*.service.ns.svc.` - * multiple wild cards are allowed in a single query. - * e.g. `A` Request `*.*.svc.zone.` or `SRV` request `*.*.*.*.svc.zone.` diff --git a/middleware/kubernetes/apiproxy.go b/middleware/kubernetes/apiproxy.go deleted file mode 100644 index f6d2c00fa..000000000 --- a/middleware/kubernetes/apiproxy.go +++ /dev/null @@ -1,76 +0,0 @@ -package kubernetes - -import ( - "fmt" - "io" - "log" - "net" - "net/http" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" -) - -type proxyHandler struct { - healthcheck.HealthCheck -} - -type apiProxy struct { - http.Server - listener net.Listener - handler proxyHandler -} - -func (p *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - upstream := p.Select() - network := "tcp" - if upstream.Network != "" { - network = upstream.Network - } - address := upstream.Name - d, err := net.Dial(network, address) - if err != nil { - log.Printf("[ERROR] Unable to establish connection to upstream %s://%s: %s", network, address, err) - http.Error(w, fmt.Sprintf("Unable to establish connection to upstream %s://%s: %s", network, address, err), 500) - return - } - hj, ok := w.(http.Hijacker) - if !ok { - log.Printf("[ERROR] Unable to establish connection: no hijacker") - http.Error(w, "Unable to establish connection: no hijacker", 500) - return - } - nc, _, err := hj.Hijack() - if err != nil { - log.Printf("[ERROR] Unable to hijack connection: %s", err) - http.Error(w, fmt.Sprintf("Unable to hijack connection: %s", err), 500) - return - } - defer nc.Close() - defer d.Close() - - err = r.Write(d) - if err != nil { - log.Printf("[ERROR] Unable to copy connection to upstream %s://%s: %s", network, address, err) - http.Error(w, fmt.Sprintf("Unable to copy connection to upstream %s://%s: %s", network, address, err), 500) - return - } - - errChan := make(chan error, 2) - cp := func(dst io.Writer, src io.Reader) { - _, err := io.Copy(dst, src) - errChan <- err - } - go cp(d, nc) - go cp(nc, d) - <-errChan -} - -func (p *apiProxy) Run() { - p.handler.Start() - p.Serve(p.listener) -} - -func (p *apiProxy) Stop() { - p.handler.Stop() - p.listener.Close() -} diff --git a/middleware/kubernetes/autopath.go b/middleware/kubernetes/autopath.go deleted file mode 100644 index be71ce326..000000000 --- a/middleware/kubernetes/autopath.go +++ /dev/null @@ -1,53 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "k8s.io/client-go/1.5/pkg/api" -) - -// AutoPath implements the AutoPathFunc call from the autopath middleware. -// It returns a per-query search path or nil indicating no searchpathing should happen. -func (k *Kubernetes) AutoPath(state request.Request) []string { - // Check if the query falls in a zone we are actually authoriative for and thus if we want autopath. - zone := middleware.Zones(k.Zones).Matches(state.Name()) - if zone == "" { - return nil - } - - ip := state.IP() - - pod := k.podWithIP(ip) - if pod == nil { - return nil - } - - search := make([]string, 3) - if zone == "." { - search[0] = pod.Namespace + ".svc." - search[1] = "svc." - search[2] = "." - } else { - search[0] = pod.Namespace + ".svc." + zone - search[1] = "svc." + zone - search[2] = zone - } - - search = append(search, k.autoPathSearch...) - search = append(search, "") // sentinal - return search -} - -// podWithIP return the api.Pod for source IP ip. It returns nil if nothing can be found. -func (k *Kubernetes) podWithIP(ip string) (p *api.Pod) { - objList := k.APIConn.PodIndex(ip) - for _, o := range objList { - p, ok := o.(*api.Pod) - if !ok { - return nil - } - return p - } - return nil -} diff --git a/middleware/kubernetes/controller.go b/middleware/kubernetes/controller.go deleted file mode 100644 index b809264e1..000000000 --- a/middleware/kubernetes/controller.go +++ /dev/null @@ -1,399 +0,0 @@ -package kubernetes - -import ( - "errors" - "fmt" - "log" - "sync" - "time" - - "k8s.io/client-go/1.5/kubernetes" - "k8s.io/client-go/1.5/pkg/api" - unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" - "k8s.io/client-go/1.5/pkg/api/v1" - "k8s.io/client-go/1.5/pkg/labels" - "k8s.io/client-go/1.5/pkg/runtime" - "k8s.io/client-go/1.5/pkg/watch" - "k8s.io/client-go/1.5/tools/cache" -) - -var ( - namespace = api.NamespaceAll -) - -// storeToNamespaceLister makes a Store that lists Namespaces. -type storeToNamespaceLister struct { - cache.Store -} - -const podIPIndex = "PodIP" - -// List lists all Namespaces in the store. -func (s *storeToNamespaceLister) List() (ns api.NamespaceList, err error) { - for _, m := range s.Store.List() { - ns.Items = append(ns.Items, *(m.(*api.Namespace))) - } - return ns, nil -} - -type dnsController interface { - ServiceList() []*api.Service - PodIndex(string) []interface{} - EndpointsList() api.EndpointsList - - GetNodeByName(string) (api.Node, error) - - Run() - Stop() error -} - -type dnsControl struct { - client *kubernetes.Clientset - - selector *labels.Selector - - svcController *cache.Controller - podController *cache.Controller - nsController *cache.Controller - epController *cache.Controller - - svcLister cache.StoreToServiceLister - podLister cache.StoreToPodLister - nsLister storeToNamespaceLister - epLister cache.StoreToEndpointsLister - - // stopLock is used to enforce only a single call to Stop is active. - // Needed because we allow stopping through an http endpoint and - // allowing concurrent stoppers leads to stack traces. - stopLock sync.Mutex - shutdown bool - stopCh chan struct{} -} - -type dnsControlOpts struct { - initPodCache bool - resyncPeriod time.Duration - // Label handling. - labelSelector *unversionedapi.LabelSelector - selector *labels.Selector -} - -// newDNSController creates a controller for CoreDNS. -func newdnsController(kubeClient *kubernetes.Clientset, opts dnsControlOpts) *dnsControl { - dns := dnsControl{ - client: kubeClient, - selector: opts.selector, - stopCh: make(chan struct{}), - } - - dns.svcLister.Indexer, dns.svcController = cache.NewIndexerInformer( - &cache.ListWatch{ - ListFunc: serviceListFunc(dns.client, namespace, dns.selector), - WatchFunc: serviceWatchFunc(dns.client, namespace, dns.selector), - }, - &api.Service{}, - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}, - cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - - if opts.initPodCache { - dns.podLister.Indexer, dns.podController = cache.NewIndexerInformer( - &cache.ListWatch{ - ListFunc: podListFunc(dns.client, namespace, dns.selector), - WatchFunc: podWatchFunc(dns.client, namespace, dns.selector), - }, - &api.Pod{}, // TODO replace with a lighter-weight custom struct - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}, - cache.Indexers{podIPIndex: podIPIndexFunc}) - } - - dns.nsLister.Store, dns.nsController = cache.NewInformer( - &cache.ListWatch{ - ListFunc: namespaceListFunc(dns.client, dns.selector), - WatchFunc: namespaceWatchFunc(dns.client, dns.selector), - }, - &api.Namespace{}, - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}) - - dns.epLister.Store, dns.epController = cache.NewInformer( - &cache.ListWatch{ - ListFunc: endpointsListFunc(dns.client, namespace, dns.selector), - WatchFunc: endpointsWatchFunc(dns.client, namespace, dns.selector), - }, - &api.Endpoints{}, - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}) - - return &dns -} - -func podIPIndexFunc(obj interface{}) ([]string, error) { - p, ok := obj.(*api.Pod) - if !ok { - return nil, errors.New("obj was not an *api.Pod") - } - return []string{p.Status.PodIP}, nil -} - -func serviceListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Services(ns).List(opts) - - if err != nil { - return nil, err - } - var listAPI api.ServiceList - err = v1.Convert_v1_ServiceList_To_api_ServiceList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - return &listAPI, err - } -} - -func podListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Pods(ns).List(opts) - - if err != nil { - return nil, err - } - var listAPI api.PodList - err = v1.Convert_v1_PodList_To_api_PodList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - - return &listAPI, err - } -} - -func v1ToAPIFilter(in watch.Event) (out watch.Event, keep bool) { - if in.Type == watch.Error { - return in, true - } - - switch v1Obj := in.Object.(type) { - case *v1.Service: - var apiObj api.Service - err := v1.Convert_v1_Service_To_api_Service(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Service: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - case *v1.Pod: - var apiObj api.Pod - err := v1.Convert_v1_Pod_To_api_Pod(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Pod: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - case *v1.Namespace: - var apiObj api.Namespace - err := v1.Convert_v1_Namespace_To_api_Namespace(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Namespace: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - case *v1.Endpoints: - var apiObj api.Endpoints - err := v1.Convert_v1_Endpoints_To_api_Endpoints(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Endpoint: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - } - - log.Printf("[WARN] Unhandled v1 type in event: %v", in) - return in, true -} - -func serviceWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Services(ns).Watch(options) - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func podWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Pods(ns).Watch(options) - - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func namespaceListFunc(c *kubernetes.Clientset, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Namespaces().List(opts) - if err != nil { - return nil, err - } - var listAPI api.NamespaceList - err = v1.Convert_v1_NamespaceList_To_api_NamespaceList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - return &listAPI, err - } -} - -func namespaceWatchFunc(c *kubernetes.Clientset, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Namespaces().Watch(options) - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func endpointsListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Endpoints(ns).List(opts) - - if err != nil { - return nil, err - } - var listAPI api.EndpointsList - err = v1.Convert_v1_EndpointsList_To_api_EndpointsList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - return &listAPI, err - } -} - -func endpointsWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Endpoints(ns).Watch(options) - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func (dns *dnsControl) controllersInSync() bool { - hs := dns.svcController.HasSynced() && - dns.nsController.HasSynced() && - dns.epController.HasSynced() - - if dns.podController != nil { - hs = hs && dns.podController.HasSynced() - } - - return hs -} - -// Stop stops the controller. -func (dns *dnsControl) Stop() error { - dns.stopLock.Lock() - defer dns.stopLock.Unlock() - - // Only try draining the workqueue if we haven't already. - if !dns.shutdown { - close(dns.stopCh) - dns.shutdown = true - - return nil - } - - return fmt.Errorf("shutdown already in progress") -} - -// Run starts the controller. -func (dns *dnsControl) Run() { - go dns.svcController.Run(dns.stopCh) - go dns.nsController.Run(dns.stopCh) - go dns.epController.Run(dns.stopCh) - if dns.podController != nil { - go dns.podController.Run(dns.stopCh) - } - <-dns.stopCh -} - -func (dns *dnsControl) NamespaceList() *api.NamespaceList { - nsList, err := dns.nsLister.List() - if err != nil { - return &api.NamespaceList{} - } - - return &nsList -} - -func (dns *dnsControl) ServiceList() []*api.Service { - svcs, err := dns.svcLister.List(labels.Everything()) - if err != nil { - return []*api.Service{} - } - - return svcs -} - -func (dns *dnsControl) PodIndex(ip string) []interface{} { - pods, err := dns.podLister.Indexer.ByIndex(podIPIndex, ip) - if err != nil { - return nil - } - - return pods -} - -func (dns *dnsControl) EndpointsList() api.EndpointsList { - epl, err := dns.epLister.List() - if err != nil { - return api.EndpointsList{} - } - - 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 -} diff --git a/middleware/kubernetes/federation.go b/middleware/kubernetes/federation.go deleted file mode 100644 index f192a4a76..000000000 --- a/middleware/kubernetes/federation.go +++ /dev/null @@ -1,45 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" -) - -// The federation node.Labels keys used. -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' - - LabelZone = "failure-domain.beta.kubernetes.io/zone" - LabelRegion = "failure-domain.beta.kubernetes.io/region" -) - -// Federations is used from the federations middleware to return the service that should be -// returned as a CNAME for federation(s) to work. -func (k *Kubernetes) Federations(state request.Request, fname, fzone string) (msg.Service, error) { - nodeName := k.localNodeName() - node, err := k.APIConn.GetNodeByName(nodeName) - if err != nil { - return msg.Service{}, err - } - r, err := parseRequest(state) - if err != nil { - return msg.Service{}, err - } - - lz := node.Labels[LabelZone] - lr := node.Labels[LabelRegion] - - if r.endpoint == "" { - return msg.Service{Host: dnsutil.Join([]string{r.service, r.namespace, fname, r.podOrSvc, lz, lr, fzone})}, nil - } - - return msg.Service{Host: dnsutil.Join([]string{r.endpoint, r.service, r.namespace, fname, r.podOrSvc, lz, lr, fzone})}, nil -} diff --git a/middleware/kubernetes/handler.go b/middleware/kubernetes/handler.go deleted file mode 100644 index e83ab3d7d..000000000 --- a/middleware/kubernetes/handler.go +++ /dev/null @@ -1,86 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// ServeDNS implements the middleware.Handler interface. -func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - - zone := middleware.Zones(k.Zones).Matches(state.Name()) - if zone == "" { - return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r) - } - - state.Zone = zone - - var ( - records []dns.RR - extra []dns.RR - err error - ) - - switch state.Type() { - case "A": - records, err = middleware.A(&k, zone, state, nil, middleware.Options{}) - case "AAAA": - records, err = middleware.AAAA(&k, zone, state, nil, middleware.Options{}) - case "TXT": - records, err = middleware.TXT(&k, zone, state, middleware.Options{}) - case "CNAME": - records, err = middleware.CNAME(&k, zone, state, middleware.Options{}) - case "PTR": - records, err = middleware.PTR(&k, zone, state, middleware.Options{}) - case "MX": - records, extra, err = middleware.MX(&k, zone, state, middleware.Options{}) - case "SRV": - records, extra, err = middleware.SRV(&k, zone, state, middleware.Options{}) - case "SOA": - records, err = middleware.SOA(&k, zone, state, middleware.Options{}) - case "NS": - if state.Name() == zone { - records, extra, err = middleware.NS(&k, zone, state, middleware.Options{}) - break - } - fallthrough - default: - // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN - _, err = middleware.A(&k, zone, state, nil, middleware.Options{}) - } - - if k.IsNameError(err) { - if k.Fallthrough { - return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r) - } - return middleware.BackendError(&k, zone, dns.RcodeNameError, state, nil /* err */, middleware.Options{}) - } - if err != nil { - return dns.RcodeServerFailure, err - } - - if len(records) == 0 { - return middleware.BackendError(&k, zone, dns.RcodeSuccess, state, nil, middleware.Options{}) - } - - m.Answer = append(m.Answer, records...) - m.Extra = append(m.Extra, extra...) - - m = dnsutil.Dedup(m) - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil -} - -// Name implements the Handler interface. -func (k Kubernetes) Name() string { return "kubernetes" } diff --git a/middleware/kubernetes/handler_pod_disabled_test.go b/middleware/kubernetes/handler_pod_disabled_test.go deleted file mode 100644 index b1fb7604c..000000000 --- a/middleware/kubernetes/handler_pod_disabled_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var podModeDisabledCases = []test.Case{ - { - Qname: "10-240-0-1.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Error: errPodsDisabled, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - { - Qname: "172-0-0-2.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Error: errPodsDisabled, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, -} - -func TestServeDNSModeDisabled(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - k.podMode = podModeDisabled - ctx := context.TODO() - - for i, tc := range podModeDisabledCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/handler_pod_insecure_test.go b/middleware/kubernetes/handler_pod_insecure_test.go deleted file mode 100644 index bc09f3c4f..000000000 --- a/middleware/kubernetes/handler_pod_insecure_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var podModeInsecureCases = []test.Case{ - { - Qname: "10-240-0-1.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("10-240-0-1.podns.pod.cluster.local. 0 IN A 10.240.0.1"), - }, - }, - { - Qname: "172-0-0-2.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("172-0-0-2.podns.pod.cluster.local. 0 IN A 172.0.0.2"), - }, - }, -} - -func TestServeDNSModeInsecure(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - k.podMode = podModeInsecure - - for i, tc := range podModeInsecureCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/handler_pod_verified_test.go b/middleware/kubernetes/handler_pod_verified_test.go deleted file mode 100644 index 879e785af..000000000 --- a/middleware/kubernetes/handler_pod_verified_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var podModeVerifiedCases = []test.Case{ - { - Qname: "10-240-0-1.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("10-240-0-1.podns.pod.cluster.local. 0 IN A 10.240.0.1"), - }, - }, - { - Qname: "172-0-0-2.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, -} - -func TestServeDNSModeVerified(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - k.podMode = podModeVerified - - for i, tc := range podModeVerifiedCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/handler_test.go b/middleware/kubernetes/handler_test.go deleted file mode 100644 index 8ab51aff8..000000000 --- a/middleware/kubernetes/handler_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" - "k8s.io/client-go/1.5/pkg/api" -) - -var dnsTestCases = []test.Case{ - // A Service - { - Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("svc1.testns.svc.cluster.local. 5 IN A 10.0.0.1"), - }, - }, - // A Service (wildcard) - { - Qname: "svc1.*.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("svc1.*.svc.cluster.local. 5 IN A 10.0.0.1"), - }, - }, - { - Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{test.SRV("svc1.testns.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")}, - Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1")}, - }, - // SRV Service (wildcard) - { - Qname: "svc1.*.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{test.SRV("svc1.*.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")}, - Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1")}, - }, - // SRV Service (wildcards) - { - Qname: "*.any.svc1.*.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{test.SRV("*.any.svc1.*.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")}, - Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1")}, - }, - // A Service (wildcards) - { - Qname: "*.any.svc1.*.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("*.any.svc1.*.svc.cluster.local. 303 IN A 10.0.0.1"), - }, - }, - // SRV Service Not udp/tcp - { - Qname: "*._not-udp-or-tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // SRV Service - { - Qname: "_http._tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.SRV("_http._tcp.svc1.testns.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local."), - }, - Extra: []dns.RR{ - test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1"), - }, - }, - // A Service (Headless) - { - Qname: "hdls1.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.2"), - test.A("hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.3"), - }, - }, - // SRV Service (Headless) - { - Qname: "_http._tcp.hdls1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.SRV("_http._tcp.hdls1.testns.svc.cluster.local. 303 IN SRV 0 50 80 172-0-0-2.hdls1.testns.svc.cluster.local."), - test.SRV("_http._tcp.hdls1.testns.svc.cluster.local. 303 IN SRV 0 50 80 172-0-0-3.hdls1.testns.svc.cluster.local."), - }, - Extra: []dns.RR{ - test.A("172-0-0-2.hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.2"), - test.A("172-0-0-3.hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.3"), - }, - }, - // CNAME External - { - Qname: "external.testns.svc.cluster.local.", Qtype: dns.TypeCNAME, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.CNAME("external.testns.svc.cluster.local. 303 IN CNAME ext.interwebs.test."), - }, - }, - // AAAA Service (existing service) - { - Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeAAAA, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // AAAA Service (non-existing service) - { - Qname: "svc0.testns.svc.cluster.local.", Qtype: dns.TypeAAAA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // A Service (non-existing service) - { - Qname: "svc0.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // TXT Schema - { - Qname: "dns-version.cluster.local.", Qtype: dns.TypeTXT, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.TXT("dns-version.cluster.local 28800 IN TXT 1.0.1"), - }, - }, - // A Service (Headless) does not exist - { - Qname: "bogusendpoint.hdls1.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // A Service does not exist - { - Qname: "bogusendpoint.svc0.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, -} - -func TestServeDNS(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - - for i, tc := range dnsTestCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - // Before sorting, make sure that CNAMES do not appear after their target records - test.CNAMEOrder(t, resp) - - test.SortAndCheck(t, resp, tc) - } -} - -type APIConnServeTest struct{} - -func (APIConnServeTest) Run() { return } -func (APIConnServeTest) Stop() error { return nil } - -func (APIConnServeTest) PodIndex(string) []interface{} { - a := make([]interface{}, 1) - a[0] = &api.Pod{ - ObjectMeta: api.ObjectMeta{ - Namespace: "podns", - }, - Status: api.PodStatus{ - PodIP: "10.240.0.1", // Remote IP set in test.ResponseWriter - }, - } - return a -} - -func (APIConnServeTest) ServiceList() []*api.Service { - svcs := []*api.Service{ - { - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: api.ClusterIPNone, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "external", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ExternalName: "ext.interwebs.test", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - } - return svcs - -} - -func (APIConnServeTest) EndpointsList() api.EndpointsList { - n := "test.node.foo.bar" - - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.1", - Hostname: "ep1a", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.2", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.3", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "10.9.8.7", - NodeName: &n, - }, - }, - }, - }, - }, - }, - } -} - -func (APIConnServeTest) GetNodeByName(name string) (api.Node, error) { - return api.Node{ - ObjectMeta: api.ObjectMeta{ - Name: "test.node.foo.bar", - }, - }, nil -} diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go deleted file mode 100644 index fe58df74d..000000000 --- a/middleware/kubernetes/kubernetes.go +++ /dev/null @@ -1,457 +0,0 @@ -// Package kubernetes provides the kubernetes backend. -package kubernetes - -import ( - "errors" - "fmt" - "net" - "strings" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "k8s.io/client-go/1.5/kubernetes" - "k8s.io/client-go/1.5/pkg/api" - unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" - "k8s.io/client-go/1.5/pkg/labels" - "k8s.io/client-go/1.5/rest" - "k8s.io/client-go/1.5/tools/clientcmd" - clientcmdapi "k8s.io/client-go/1.5/tools/clientcmd/api" -) - -// Kubernetes implements a middleware that connects to a Kubernetes cluster. -type Kubernetes struct { - Next middleware.Handler - Zones []string - Proxy proxy.Proxy // Proxy for looking up names during the resolution process - APIServerList []string - APIProxy *apiProxy - APICertAuth string - APIClientCert string - APIClientKey string - APIConn dnsController - Namespaces map[string]bool - podMode string - Fallthrough bool - ttl uint32 - - primaryZoneIndex int - interfaceAddrsFunc func() net.IP - autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath. -} - -// New returns a intialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other -// values default to their zero value, primaryZoneIndex will thus point to the first zone. -func New(zones []string) *Kubernetes { - k := new(Kubernetes) - k.Zones = zones - k.Namespaces = make(map[string]bool) - k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("127.0.0.1") } - k.podMode = podModeDisabled - k.Proxy = proxy.Proxy{} - k.ttl = defaultTTL - - return k -} - -const ( - // podModeDisabled is the default value where pod requests are ignored - podModeDisabled = "disabled" - // podModeVerified is where Pod requests are answered only if they exist - podModeVerified = "verified" - // podModeInsecure is where pod requests are answered without verfying they exist - podModeInsecure = "insecure" - // DNSSchemaVersion is the schema version: https://github.com/kubernetes/dns/blob/master/docs/specification.md - DNSSchemaVersion = "1.0.1" -) - -var ( - errNoItems = errors.New("no items found") - errNsNotExposed = errors.New("namespace is not exposed") - errInvalidRequest = errors.New("invalid query name") - errAPIBadPodType = errors.New("expected type *api.Pod") - errPodsDisabled = errors.New("pod records disabled") -) - -// Services implements the ServiceBackend interface. -func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) (svcs []msg.Service, err error) { - - // We're looking again at types, which we've already done in ServeDNS, but there are some types k8s just can't answer. - switch state.QType() { - - case dns.TypeTXT: - // 1 label + zone, label must be "dns-version". - t, _ := dnsutil.TrimZone(state.Name(), state.Zone) - - segs := dns.SplitDomainName(t) - if len(segs) != 1 { - return nil, fmt.Errorf("kubernetes: TXT query can only be for dns-version: %s", state.QName()) - } - if segs[0] != "dns-version" { - return nil, nil - } - svc := msg.Service{Text: DNSSchemaVersion, TTL: 28800, Key: msg.Path(state.QName(), "coredns")} - return []msg.Service{svc}, nil - - case dns.TypeNS: - // We can only get here if the qname equal the zone, see ServeDNS in handler.go. - ns := k.nsAddr() - svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")} - return []msg.Service{svc}, nil - } - - if state.QType() == dns.TypeA && isDefaultNS(state.Name(), state.Zone) { - // If this is an A request for "ns.dns", respond with a "fake" record for coredns. - // SOA records always use this hardcoded name - ns := k.nsAddr() - svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")} - return []msg.Service{svc}, nil - } - - s, e := k.Records(state, false) - - // SRV for external services is not yet implemented, so remove those records. - - if state.QType() != dns.TypeSRV { - return s, e - } - - internal := []msg.Service{} - for _, svc := range s { - if t, _ := svc.HostType(); t != dns.TypeCNAME { - internal = append(internal, svc) - } - } - - return internal, e -} - -// primaryZone will return the first non-reverse zone being handled by this middleware -func (k *Kubernetes) primaryZone() string { return k.Zones[k.primaryZoneIndex] } - -// Lookup implements the ServiceBackend interface. -func (k *Kubernetes) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { - return k.Proxy.Lookup(state, name, typ) -} - -// IsNameError implements the ServiceBackend interface. -func (k *Kubernetes) IsNameError(err error) bool { - return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest -} - -func (k *Kubernetes) getClientConfig() (*rest.Config, error) { - loadingRules := &clientcmd.ClientConfigLoadingRules{} - overrides := &clientcmd.ConfigOverrides{} - clusterinfo := clientcmdapi.Cluster{} - authinfo := clientcmdapi.AuthInfo{} - - if len(k.APIServerList) == 0 { - cc, err := rest.InClusterConfig() - if err != nil { - return nil, err - } - return cc, err - } - - endpoint := k.APIServerList[0] - if len(k.APIServerList) > 1 { - // Use a random port for api proxy, will get the value later through listener.Addr() - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return nil, fmt.Errorf("failed to create kubernetes api proxy: %v", err) - } - k.APIProxy = &apiProxy{ - listener: listener, - handler: proxyHandler{ - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 3 * time.Second, - MaxFails: 1, - Future: 10 * time.Second, - Path: "/", - Interval: 5 * time.Second, - }, - }, - } - k.APIProxy.handler.Hosts = make([]*healthcheck.UpstreamHost, len(k.APIServerList)) - for i, entry := range k.APIServerList { - - uh := &healthcheck.UpstreamHost{ - Name: strings.TrimPrefix(entry, "http://"), - - CheckDown: func(upstream *proxyHandler) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(&k.APIProxy.handler), - } - - k.APIProxy.handler.Hosts[i] = uh - } - k.APIProxy.Handler = &k.APIProxy.handler - - // Find the random port used for api proxy - endpoint = fmt.Sprintf("http://%s", listener.Addr()) - } - clusterinfo.Server = endpoint - - if len(k.APICertAuth) > 0 { - clusterinfo.CertificateAuthority = k.APICertAuth - } - if len(k.APIClientCert) > 0 { - authinfo.ClientCertificate = k.APIClientCert - } - if len(k.APIClientKey) > 0 { - authinfo.ClientKey = k.APIClientKey - } - - overrides.ClusterInfo = clusterinfo - overrides.AuthInfo = authinfo - clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) - - return clientConfig.ClientConfig() -} - -// initKubeCache initializes a new Kubernetes cache. -func (k *Kubernetes) initKubeCache(opts dnsControlOpts) (err error) { - - config, err := k.getClientConfig() - if err != nil { - return err - } - - kubeClient, err := kubernetes.NewForConfig(config) - if err != nil { - return fmt.Errorf("failed to create kubernetes notification controller: %q", err) - } - - if opts.labelSelector != nil { - var selector labels.Selector - selector, err = unversionedapi.LabelSelectorAsSelector(opts.labelSelector) - if err != nil { - return fmt.Errorf("unable to create Selector for LabelSelector '%s': %q", opts.labelSelector, err) - } - opts.selector = &selector - } - - opts.initPodCache = k.podMode == podModeVerified - - k.APIConn = newdnsController(kubeClient, opts) - - return err -} - -// Records looks up services in kubernetes. -func (k *Kubernetes) Records(state request.Request, exact bool) ([]msg.Service, error) { - r, e := parseRequest(state) - if e != nil { - return nil, e - } - - if !wildcard(r.namespace) && !k.namespaceExposed(r.namespace) { - return nil, errNsNotExposed - } - - if r.podOrSvc == Pod { - pods, err := k.findPods(r, state.Zone) - return pods, err - } - - services, err := k.findServices(r, state.Zone) - return services, err -} - -func endpointHostname(addr api.EndpointAddress) string { - if addr.Hostname != "" { - return strings.ToLower(addr.Hostname) - } - if strings.Contains(addr.IP, ".") { - return strings.Replace(addr.IP, ".", "-", -1) - } - if strings.Contains(addr.IP, ":") { - return strings.ToLower(strings.Replace(addr.IP, ":", "-", -1)) - } - return "" -} - -func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service, err error) { - if k.podMode == podModeDisabled { - return nil, errPodsDisabled - } - - namespace := r.namespace - podname := r.service - zonePath := msg.Path(zone, "coredns") - ip := "" - err = errNoItems - - if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") { - ip = strings.Replace(podname, "-", ".", -1) - } else { - ip = strings.Replace(podname, "-", ":", -1) - } - - if k.podMode == podModeInsecure { - return []msg.Service{{Key: strings.Join([]string{zonePath, Pod, namespace, podname}, "/"), Host: ip}}, nil - } - - // PodModeVerified - objList := k.APIConn.PodIndex(ip) - - for _, o := range objList { - p, ok := o.(*api.Pod) - if !ok { - return nil, errAPIBadPodType - } - // If namespace has a wildcard, filter results against Corefile namespace list. - if wildcard(namespace) && !k.namespaceExposed(p.Namespace) { - continue - } - // check for matching ip and namespace - if ip == p.Status.PodIP && match(namespace, p.Namespace) { - s := msg.Service{Key: strings.Join([]string{zonePath, Pod, namespace, podname}, "/"), Host: ip} - pods = append(pods, s) - - err = nil - } - } - return pods, err -} - -// findServices returns the services matching r from the cache. -func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.Service, err error) { - serviceList := k.APIConn.ServiceList() - zonePath := msg.Path(zone, "coredns") - err = errNoItems // Set to errNoItems to signal really nothing found, gets reset when name is matched. - - for _, svc := range serviceList { - if !(match(r.namespace, svc.Namespace) && match(r.service, svc.Name)) { - continue - } - - // If namespace has a wildcard, filter results against Corefile namespace list. - // (Namespaces without a wildcard were filtered before the call to this function.) - if wildcard(r.namespace) && !k.namespaceExposed(svc.Namespace) { - continue - } - - // Endpoint query or headless service - if svc.Spec.ClusterIP == api.ClusterIPNone || r.endpoint != "" { - endpointsList := k.APIConn.EndpointsList() - for _, ep := range endpointsList.Items { - if ep.ObjectMeta.Name != svc.Name || ep.ObjectMeta.Namespace != svc.Namespace { - continue - } - - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - - // See comments in parse.go parseRequest about the endpoint handling. - - if r.endpoint != "" { - if !match(r.endpoint, endpointHostname(addr)) { - continue - } - } - - for _, p := range eps.Ports { - if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { - continue - } - s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl} - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name, endpointHostname(addr)}, "/") - - err = nil - - services = append(services, s) - } - } - } - } - continue - } - - // External service - if svc.Spec.ExternalName != "" { - s := msg.Service{Key: strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/"), Host: svc.Spec.ExternalName, TTL: k.ttl} - if t, _ := s.HostType(); t == dns.TypeCNAME { - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") - services = append(services, s) - - err = nil - - continue - } - } - - // ClusterIP service - for _, p := range svc.Spec.Ports { - if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { - continue - } - - err = nil - - s := msg.Service{Host: svc.Spec.ClusterIP, Port: int(p.Port), TTL: k.ttl} - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") - - services = append(services, s) - } - } - return services, err -} - -// match checks if a and b are equal taking wildcards into account. -func match(a, b string) bool { - if wildcard(a) { - return true - } - if wildcard(b) { - return true - } - return strings.EqualFold(a, b) -} - -// wildcard checks whether s contains a wildcard value defined as "*" or "any". -func wildcard(s string) bool { - return s == "*" || s == "any" -} - -// namespaceExposed returns true when the namespace is exposed. -func (k *Kubernetes) namespaceExposed(namespace string) bool { - _, ok := k.Namespaces[namespace] - if len(k.Namespaces) > 0 && !ok { - return false - } - return true -} - -const ( - // Svc is the DNS schema for kubernetes services - Svc = "svc" - // Pod is the DNS schema for kubernetes pods - Pod = "pod" - // defaultTTL to apply to all answers. - defaultTTL = 5 -) diff --git a/middleware/kubernetes/kubernetes_apex_test.go b/middleware/kubernetes/kubernetes_apex_test.go deleted file mode 100644 index 9a87a790a..000000000 --- a/middleware/kubernetes/kubernetes_apex_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var kubeApexCases = [](test.Case){ - { - Qname: "cluster.local.", Qtype: dns.TypeSOA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.SOA("cluster.local. 303 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - { - Qname: "cluster.local.", Qtype: dns.TypeHINFO, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("cluster.local. 303 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - { - Qname: "cluster.local.", Qtype: dns.TypeNS, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.NS("cluster.local. 303 IN NS ns.dns.cluster.local."), - }, - Extra: []dns.RR{ - test.A("ns.dns.cluster.local. 303 IN A 127.0.0.1"), - }, - }, -} - -func TestServeDNSApex(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - - for i, tc := range kubeApexCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d, expected no error, got %v\n", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error ford", i) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/kubernetes_test.go b/middleware/kubernetes/kubernetes_test.go deleted file mode 100644 index 573a517b1..000000000 --- a/middleware/kubernetes/kubernetes_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "k8s.io/client-go/1.5/pkg/api" -) - -func TestWildcard(t *testing.T) { - var tests = []struct { - s string - expected bool - }{ - {"mynamespace", false}, - {"*", true}, - {"any", true}, - {"my*space", false}, - {"*space", false}, - {"myname*", false}, - } - - for _, te := range tests { - got := wildcard(te.s) - if got != te.expected { - t.Errorf("Expected Wildcard result '%v' for example '%v', got '%v'.", te.expected, te.s, got) - } - } -} - -func TestEndpointHostname(t *testing.T) { - var tests = []struct { - ip string - hostname string - expected string - }{ - {"10.11.12.13", "", "10-11-12-13"}, - {"10.11.12.13", "epname", "epname"}, - } - for _, test := range tests { - result := endpointHostname(api.EndpointAddress{IP: test.ip, Hostname: test.hostname}) - if result != test.expected { - t.Errorf("Expected endpoint name for (ip:%v hostname:%v) to be '%v', but got '%v'", test.ip, test.hostname, test.expected, result) - } - } -} - -type APIConnServiceTest struct{} - -func (APIConnServiceTest) Run() { return } -func (APIConnServiceTest) Stop() error { return nil } -func (APIConnServiceTest) PodIndex(string) []interface{} { return nil } - -func (APIConnServiceTest) ServiceList() []*api.Service { - svcs := []*api.Service{ - { - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: api.ClusterIPNone, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "external", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ExternalName: "coredns.io", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - } - return svcs -} - -func (APIConnServiceTest) EndpointsList() api.EndpointsList { - n := "test.node.foo.bar" - - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.1", - Hostname: "ep1a", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.2", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.3", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - 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", - }, - }, nil -} - -func TestServices(t *testing.T) { - - k := New([]string{"interwebs.test."}) - k.APIConn = &APIConnServiceTest{} - - type svcAns struct { - host string - key string - } - type svcTest struct { - qname string - qtype uint16 - answer svcAns - } - tests := []svcTest{ - // Cluster IP Services - {qname: "svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "10.0.0.1", key: "/coredns/test/interwebs/svc/testns/svc1"}}, - {qname: "_http._tcp.svc1.testns.svc.interwebs.test.", qtype: dns.TypeSRV, answer: svcAns{host: "10.0.0.1", key: "/coredns/test/interwebs/svc/testns/svc1"}}, - {qname: "ep1a.svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "172.0.0.1", key: "/coredns/test/interwebs/svc/testns/svc1/ep1a"}}, - - // External Services - {qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: svcAns{host: "coredns.io", key: "/coredns/test/interwebs/svc/testns/external"}}, - } - - for i, test := range tests { - state := request.Request{ - Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}}, - Zone: "interwebs.test.", // must match from k.Zones[0] - } - svcs, e := k.Services(state, false, middleware.Options{}) - if e != nil { - t.Errorf("Test %d: got error '%v'", i, e) - continue - } - if len(svcs) != 1 { - t.Errorf("Test %d, expected expected 1 answer, got %v", i, len(svcs)) - continue - } - - if test.answer.host != svcs[0].Host { - t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host) - } - if test.answer.key != svcs[0].Key { - t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key) - } - } -} diff --git a/middleware/kubernetes/local.go b/middleware/kubernetes/local.go deleted file mode 100644 index e5b7f1e0f..000000000 --- a/middleware/kubernetes/local.go +++ /dev/null @@ -1,40 +0,0 @@ -package kubernetes - -import "net" - -func localPodIP() net.IP { - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil - } - - for _, addr := range addrs { - ip, _, _ := net.ParseCIDR(addr.String()) - ip = ip.To4() - if ip == nil || ip.IsLoopback() { - continue - } - return ip - } - return nil -} - -func (k *Kubernetes) localNodeName() string { - localIP := k.interfaceAddrsFunc() - 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)) { - return *addr.NodeName - } - } - } - } - return "" -} diff --git a/middleware/kubernetes/ns.go b/middleware/kubernetes/ns.go deleted file mode 100644 index 4cacc382f..000000000 --- a/middleware/kubernetes/ns.go +++ /dev/null @@ -1,65 +0,0 @@ -package kubernetes - -import ( - "net" - "strings" - - "github.com/miekg/dns" - "k8s.io/client-go/1.5/pkg/api" -) - -func isDefaultNS(name, zone string) bool { - return strings.Index(name, defaultNSName) == 0 && strings.Index(name, zone) == len(defaultNSName) -} - -func (k *Kubernetes) nsAddr() *dns.A { - var ( - svcName string - svcNamespace string - ) - - rr := new(dns.A) - localIP := k.interfaceAddrsFunc() - endpointsList := k.APIConn.EndpointsList() - - rr.A = localIP - -FindEndpoint: - for _, ep := range endpointsList.Items { - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - if localIP.Equal(net.ParseIP(addr.IP)) { - svcNamespace = ep.ObjectMeta.Namespace - svcName = ep.ObjectMeta.Name - break FindEndpoint - } - } - } - } - - if len(svcName) == 0 { - rr.Hdr.Name = defaultNSName - rr.A = localIP - return rr - } - // Find service to get ClusterIP - serviceList := k.APIConn.ServiceList() - -FindService: - for _, svc := range serviceList { - if svcName == svc.Name && svcNamespace == svc.Namespace { - if svc.Spec.ClusterIP == api.ClusterIPNone { - rr.A = localIP - } else { - rr.A = net.ParseIP(svc.Spec.ClusterIP) - } - break FindService - } - } - - rr.Hdr.Name = strings.Join([]string{svcName, svcNamespace, "svc."}, ".") - - return rr -} - -const defaultNSName = "ns.dns." diff --git a/middleware/kubernetes/ns_test.go b/middleware/kubernetes/ns_test.go deleted file mode 100644 index 8e9e80c71..000000000 --- a/middleware/kubernetes/ns_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package kubernetes - -import ( - "testing" - - "k8s.io/client-go/1.5/pkg/api" -) - -type APIConnTest struct{} - -func (APIConnTest) Run() { return } -func (APIConnTest) Stop() error { return nil } -func (APIConnTest) PodIndex(string) []interface{} { return nil } - -func (APIConnTest) ServiceList() []*api.Service { - svc := api.Service{ - ObjectMeta: api.ObjectMeta{ - Name: "dns-service", - Namespace: "kube-system", - }, - Spec: api.ServiceSpec{ - ClusterIP: "10.0.0.111", - }, - } - - return []*api.Service{&svc} - -} - -func (APIConnTest) EndpointsList() api.EndpointsList { - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "127.0.0.1", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "dns-service", - Namespace: "kube-system", - }, - }, - }, - } -} - -func (APIConnTest) GetNodeByName(name string) (api.Node, error) { return api.Node{}, nil } - -func TestNsAddr(t *testing.T) { - - k := New([]string{"inter.webs.test."}) - k.APIConn = &APIConnTest{} - - cdr := k.nsAddr() - expected := "10.0.0.111" - - if cdr.A.String() != expected { - t.Errorf("Expected A to be %q, got %q", expected, cdr.A.String()) - } - expected = "dns-service.kube-system.svc." - if cdr.Hdr.Name != expected { - t.Errorf("Expected Hdr.Name to be %q, got %q", expected, cdr.Hdr.Name) - } -} diff --git a/middleware/kubernetes/parse.go b/middleware/kubernetes/parse.go deleted file mode 100644 index bf8adfec3..000000000 --- a/middleware/kubernetes/parse.go +++ /dev/null @@ -1,112 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -type recordRequest struct { - // The named port from the kubernetes DNS spec, this is the service part (think _https) from a well formed - // SRV record. - port string - // The protocol is usually _udp or _tcp (if set), and comes from the protocol part of a well formed - // SRV record. - protocol string - endpoint string - // The servicename used in Kubernetes. - service string - // The namespace used in Kubernetes. - namespace string - // A each name can be for a pod or a service, here we track what we've seen, either "pod" or "service". - podOrSvc string -} - -// parseRequest parses the qname to find all the elements we need for querying k8s. Anything -// that is not parsed will have the wildcard "*" value (except r.endpoint). -// Potential underscores are stripped from _port and _protocol. -func parseRequest(state request.Request) (r recordRequest, err error) { - // 3 Possible cases: - // 1. _port._protocol.service.namespace.pod|svc.zone - // 2. (endpoint): endpoint.service.namespace.pod|svc.zone - // 3. (service): service.namespace.pod|svc.zone - // - // Federations are handled in the federation middleware. And aren't parsed here. - - base, _ := dnsutil.TrimZone(state.Name(), state.Zone) - segs := dns.SplitDomainName(base) - - r.port = "*" - r.protocol = "*" - r.service = "*" - r.namespace = "*" - // r.endpoint is the odd one out, we need to know if it has been set or not. If it is - // empty we should skip the endpoint check in k.get(). Hence we cannot set if to "*". - - // start at the right and fill out recordRequest with the bits we find, so we look for - // pod|svc.namespace.service and then either - // * endpoint - // *_protocol._port - - last := len(segs) - 1 - if last < 0 { - return r, nil - } - r.podOrSvc = segs[last] - if r.podOrSvc != Pod && r.podOrSvc != Svc { - return r, errInvalidRequest - } - last-- - if last < 0 { - return r, nil - } - - r.namespace = segs[last] - last-- - if last < 0 { - return r, nil - } - - r.service = segs[last] - last-- - if last < 0 { - return r, nil - } - - // Because of ambiquity we check the labels left: 1: an endpoint. 2: port and protocol. - // Anything else is a query that is too long to answer and can safely be delegated to return an nxdomain. - switch last { - - case 0: // endpoint only - r.endpoint = segs[last] - case 1: // service and port - r.protocol = stripUnderscore(segs[last]) - r.port = stripUnderscore(segs[last-1]) - - default: // too long - return r, errInvalidRequest - } - - return r, nil -} - -// stripUnderscore removes a prefixed underscore from s. -func stripUnderscore(s string) string { - if s[0] != '_' { - return s - } - return s[1:] -} - -// String return a string representation of r, it just returns all fields concatenated with dots. -// This is mostly used in tests. -func (r recordRequest) String() string { - s := r.port - s += "." + r.protocol - s += "." + r.endpoint - s += "." + r.service - s += "." + r.namespace - s += "." + r.podOrSvc - return s -} diff --git a/middleware/kubernetes/parse_test.go b/middleware/kubernetes/parse_test.go deleted file mode 100644 index 06d5a2aaa..000000000 --- a/middleware/kubernetes/parse_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -func TestParseRequest(t *testing.T) { - tests := []struct { - query string - expected string // output from r.String() - }{ - // valid SRV request - {"_http._tcp.webs.mynamespace.svc.inter.webs.test.", "http.tcp..webs.mynamespace.svc"}, - // wildcard acceptance - {"*.any.*.any.svc.inter.webs.test.", "*.any..*.any.svc"}, - // A request of endpoint - {"1-2-3-4.webs.mynamespace.svc.inter.webs.test.", "*.*.1-2-3-4.webs.mynamespace.svc"}, - } - for i, tc := range tests { - m := new(dns.Msg) - m.SetQuestion(tc.query, dns.TypeA) - state := request.Request{Zone: zone, Req: m} - - r, e := parseRequest(state) - if e != nil { - t.Errorf("Test %d, expected no error, got '%v'.", i, e) - } - rs := r.String() - if rs != tc.expected { - t.Errorf("Test %d, expected (stringyfied) recordRequest: %s, got %s", i, tc.expected, rs) - } - } -} - -func TestParseInvalidRequest(t *testing.T) { - invalid := []string{ - "webs.mynamespace.pood.inter.webs.test.", // Request must be for pod or svc subdomain. - "too.long.for.what.I.am.trying.to.pod.inter.webs.tests.", // Too long. - } - - for i, query := range invalid { - m := new(dns.Msg) - m.SetQuestion(query, dns.TypeA) - state := request.Request{Zone: zone, Req: m} - - if _, e := parseRequest(state); e == nil { - t.Errorf("Test %d: expected error from %s, got none", i, query) - } - } -} - -const zone = "intern.webs.tests." diff --git a/middleware/kubernetes/reverse.go b/middleware/kubernetes/reverse.go deleted file mode 100644 index 25ece8035..000000000 --- a/middleware/kubernetes/reverse.go +++ /dev/null @@ -1,55 +0,0 @@ -package kubernetes - -import ( - "strings" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" -) - -// Reverse implements the ServiceBackend interface. -func (k *Kubernetes) Reverse(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, error) { - - ip := dnsutil.ExtractAddressFromReverse(state.Name()) - if ip == "" { - return nil, nil - } - - records := k.serviceRecordForIP(ip, state.Name()) - return records, nil -} - -// serviceRecordForIP gets a service record with a cluster ip matching the ip argument -// If a service cluster ip does not match, it checks all endpoints -func (k *Kubernetes) serviceRecordForIP(ip, name string) []msg.Service { - // First check services with cluster ips - svcList := k.APIConn.ServiceList() - - for _, service := range svcList { - if (len(k.Namespaces) > 0) && !k.namespaceExposed(service.Namespace) { - continue - } - if service.Spec.ClusterIP == ip { - domain := strings.Join([]string{service.Name, service.Namespace, Svc, k.primaryZone()}, ".") - return []msg.Service{{Host: domain}} - } - } - // If no cluster ips match, search endpoints - epList := k.APIConn.EndpointsList() - for _, ep := range epList.Items { - if (len(k.Namespaces) > 0) && !k.namespaceExposed(ep.ObjectMeta.Namespace) { - continue - } - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - if addr.IP == ip { - domain := strings.Join([]string{endpointHostname(addr), ep.ObjectMeta.Name, ep.ObjectMeta.Namespace, Svc, k.primaryZone()}, ".") - return []msg.Service{{Host: domain}} - } - } - } - } - return nil -} diff --git a/middleware/kubernetes/reverse_test.go b/middleware/kubernetes/reverse_test.go deleted file mode 100644 index aaf0907e8..000000000 --- a/middleware/kubernetes/reverse_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" - "k8s.io/client-go/1.5/pkg/api" -) - -type APIConnReverseTest struct{} - -func (APIConnReverseTest) Run() { return } -func (APIConnReverseTest) Stop() error { return nil } -func (APIConnReverseTest) PodIndex(string) []interface{} { return nil } - -func (APIConnReverseTest) ServiceList() []*api.Service { - svcs := []*api.Service{ - { - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: "192.168.1.100", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - } - return svcs -} - -func (APIConnReverseTest) EndpointsList() api.EndpointsList { - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "10.0.0.100", - Hostname: "ep1a", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - }, - }, - } -} - -func (APIConnReverseTest) GetNodeByName(name string) (api.Node, error) { - return api.Node{ - ObjectMeta: api.ObjectMeta{ - Name: "test.node.foo.bar", - }, - }, nil -} - -func TestReverse(t *testing.T) { - - k := New([]string{"cluster.local.", "0.10.in-addr.arpa."}) - k.APIConn = &APIConnReverseTest{} - - tests := []test.Case{ - { - Qname: "100.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.PTR("100.0.0.10.in-addr.arpa. 303 IN PTR ep1a.svc1.testns.svc.cluster.local."), - }, - }, - { - Qname: "101.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("0.10.in-addr.arpa. 300 IN SOA ns.dns.0.10.in-addr.arpa. hostmaster.0.10.in-addr.arpa. 1502782828 7200 1800 86400 60"), - }, - }, - { - Qname: "example.org.cluster.local.", Qtype: dns.TypePTR, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1502989566 7200 1800 86400 60"), - }, - }, - } - - ctx := context.TODO() - for i, tc := range tests { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d: expected no error, got %v", i, err) - return - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d: got nil message and no error for: %s %d", i, r.Question[0].Name, r.Question[0].Qtype) - } - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go deleted file mode 100644 index 15b87c2cd..000000000 --- a/middleware/kubernetes/setup.go +++ /dev/null @@ -1,208 +0,0 @@ -package kubernetes - -import ( - "errors" - "fmt" - "strconv" - "strings" - "time" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/proxy" - "github.com/miekg/dns" - - "github.com/mholt/caddy" - unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" -) - -func init() { - caddy.RegisterPlugin("kubernetes", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - kubernetes, initOpts, err := kubernetesParse(c) - if err != nil { - return middleware.Error("kubernetes", err) - } - - err = kubernetes.initKubeCache(initOpts) - if err != nil { - return middleware.Error("kubernetes", err) - } - - // Register KubeCache start and stop functions with Caddy - c.OnStartup(func() error { - go kubernetes.APIConn.Run() - if kubernetes.APIProxy != nil { - go kubernetes.APIProxy.Run() - } - return nil - }) - - c.OnShutdown(func() error { - if kubernetes.APIProxy != nil { - kubernetes.APIProxy.Stop() - } - return kubernetes.APIConn.Stop() - }) - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - kubernetes.Next = next - return kubernetes - }) - - return nil -} - -func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) { - k8s := New([]string{""}) - k8s.interfaceAddrsFunc = localPodIP - k8s.autoPathSearch = searchFromResolvConf() - - opts := dnsControlOpts{ - resyncPeriod: defaultResyncPeriod, - } - - for c.Next() { - zones := c.RemainingArgs() - - if len(zones) != 0 { - k8s.Zones = zones - for i := 0; i < len(k8s.Zones); i++ { - k8s.Zones[i] = middleware.Host(k8s.Zones[i]).Normalize() - } - } else { - k8s.Zones = make([]string, len(c.ServerBlockKeys)) - for i := 0; i < len(c.ServerBlockKeys); i++ { - k8s.Zones[i] = middleware.Host(c.ServerBlockKeys[i]).Normalize() - } - } - - k8s.primaryZoneIndex = -1 - for i, z := range k8s.Zones { - if strings.HasSuffix(z, "in-addr.arpa.") || strings.HasSuffix(z, "ip6.arpa.") { - continue - } - k8s.primaryZoneIndex = i - break - } - - if k8s.primaryZoneIndex == -1 { - return nil, opts, errors.New("non-reverse zone name must be used") - } - - for c.NextBlock() { - switch c.Val() { - case "pods": - args := c.RemainingArgs() - if len(args) == 1 { - switch args[0] { - case podModeDisabled, podModeInsecure, podModeVerified: - k8s.podMode = args[0] - default: - return nil, opts, fmt.Errorf("wrong value for pods: %s, must be one of: disabled, verified, insecure", args[0]) - } - continue - } - return nil, opts, c.ArgErr() - case "namespaces": - args := c.RemainingArgs() - if len(args) > 0 { - for _, a := range args { - k8s.Namespaces[a] = true - } - continue - } - return nil, opts, c.ArgErr() - case "endpoint": - args := c.RemainingArgs() - if len(args) > 0 { - for _, endpoint := range strings.Split(args[0], ",") { - k8s.APIServerList = append(k8s.APIServerList, strings.TrimSpace(endpoint)) - } - continue - } - return nil, opts, c.ArgErr() - case "tls": // cert key cacertfile - args := c.RemainingArgs() - if len(args) == 3 { - k8s.APIClientCert, k8s.APIClientKey, k8s.APICertAuth = args[0], args[1], args[2] - continue - } - return nil, opts, c.ArgErr() - case "resyncperiod": - args := c.RemainingArgs() - if len(args) > 0 { - rp, err := time.ParseDuration(args[0]) - if err != nil { - return nil, opts, fmt.Errorf("unable to parse resync duration value: '%v': %v", args[0], err) - } - opts.resyncPeriod = rp - continue - } - return nil, opts, c.ArgErr() - case "labels": - args := c.RemainingArgs() - if len(args) > 0 { - labelSelectorString := strings.Join(args, " ") - ls, err := unversionedapi.ParseToLabelSelector(labelSelectorString) - if err != nil { - return nil, opts, fmt.Errorf("unable to parse label selector value: '%v': %v", labelSelectorString, err) - } - opts.labelSelector = ls - continue - } - return nil, opts, c.ArgErr() - case "fallthrough": - args := c.RemainingArgs() - if len(args) == 0 { - k8s.Fallthrough = true - continue - } - return nil, opts, c.ArgErr() - case "upstream": - args := c.RemainingArgs() - if len(args) == 0 { - return nil, opts, c.ArgErr() - } - ups, err := dnsutil.ParseHostPortOrFile(args...) - if err != nil { - return nil, opts, err - } - k8s.Proxy = proxy.NewLookup(ups) - case "ttl": - args := c.RemainingArgs() - if len(args) == 0 { - return nil, opts, c.ArgErr() - } - t, err := strconv.Atoi(args[0]) - if err != nil { - return nil, opts, err - } - if t < 5 || t > 3600 { - return nil, opts, c.Errf("ttl must be in range [5, 3600]: %d", t) - } - k8s.ttl = uint32(t) - default: - return nil, opts, c.Errf("unknown property '%s'", c.Val()) - } - } - } - return k8s, opts, nil -} - -func searchFromResolvConf() []string { - rc, err := dns.ClientConfigFromFile("/etc/resolv.conf") - if err != nil { - return nil - } - middleware.Zones(rc.Search).Normalize() - return rc.Search -} - -const defaultResyncPeriod = 5 * time.Minute diff --git a/middleware/kubernetes/setup_reverse_test.go b/middleware/kubernetes/setup_reverse_test.go deleted file mode 100644 index ed51a7410..000000000 --- a/middleware/kubernetes/setup_reverse_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestKubernetesParseReverseZone(t *testing.T) { - tests := []struct { - input string // Corefile data as string - expectedZones []string // expected count of defined zones. - }{ - {`kubernetes coredns.local 10.0.0.0/16`, []string{"coredns.local.", "0.10.in-addr.arpa."}}, - {`kubernetes coredns.local 10.0.0.0/17`, []string{"coredns.local.", "10.0.0.0/17."}}, - } - - for i, tc := range tests { - c := caddy.NewTestController("dns", tc.input) - k, _, err := kubernetesParse(c) - if err != nil { - t.Fatalf("Test %d: Expected no error, got %q", i, err) - } - - zl := len(k.Zones) - if zl != len(tc.expectedZones) { - t.Errorf("Test %d: Expected kubernetes to be initialized with %d zones, found %d zones", i, len(tc.expectedZones), zl) - } - for i, z := range tc.expectedZones { - if k.Zones[i] != z { - t.Errorf("Test %d: Expected zones to be %q, got %q", i, z, k.Zones[i]) - } - } - } -} diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go deleted file mode 100644 index 2fdc38a9c..000000000 --- a/middleware/kubernetes/setup_test.go +++ /dev/null @@ -1,473 +0,0 @@ -package kubernetes - -import ( - "strings" - "testing" - "time" - - "github.com/mholt/caddy" - "k8s.io/client-go/1.5/pkg/api/unversioned" -) - -func TestKubernetesParse(t *testing.T) { - tests := []struct { - input string // Corefile data as string - shouldErr bool // true if test case is exected to produce an error. - expectedErrContent string // substring from the expected error. Empty for positive cases. - expectedZoneCount int // expected count of defined zones. - expectedNSCount int // expected count of namespaces. - expectedResyncPeriod time.Duration // expected resync period value - expectedLabelSelector string // expected label selector value - expectedPodMode string - expectedFallthrough bool - expectedUpstreams []string - }{ - // positive - { - `kubernetes coredns.local`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local test.local`, - false, - "", - 2, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - endpoint http://localhost:9090 -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - namespaces demo -}`, - false, - "", - 1, - 1, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - namespaces demo test -}`, - false, - "", - 1, - 2, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod 30s -}`, - false, - "", - 1, - 0, - 30 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod 15m -}`, - false, - "", - 1, - 0, - 15 * time.Minute, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels environment=prod -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "environment=prod", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels environment in (production, staging, qa),application=nginx -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "application=nginx,environment in (production,qa,staging)", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local test.local { - resyncperiod 15m - endpoint http://localhost:8080 - namespaces demo test - labels environment in (production, staging, qa),application=nginx - fallthrough -}`, - false, - "", - 2, - 2, - 15 * time.Minute, - "application=nginx,environment in (production,qa,staging)", - podModeDisabled, - true, - nil, - }, - // negative - { - `kubernetes coredns.local { - endpoint -}`, - true, - "rong argument count or unexpected line ending", - -1, - -1, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - namespaces -}`, - true, - "rong argument count or unexpected line ending", - -1, - -1, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod -}`, - true, - "rong argument count or unexpected line ending", - -1, - 0, - 0 * time.Minute, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod 15 -}`, - true, - "unable to parse resync duration value", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod abc -}`, - true, - "unable to parse resync duration value", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels -}`, - true, - "rong argument count or unexpected line ending", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels environment in (production, qa -}`, - true, - "unable to parse label selector", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - // pods disabled - { - `kubernetes coredns.local { - pods disabled -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - // pods insecure - { - `kubernetes coredns.local { - pods insecure -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeInsecure, - false, - nil, - }, - // pods verified - { - `kubernetes coredns.local { - pods verified -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeVerified, - false, - nil, - }, - // pods invalid - { - `kubernetes coredns.local { - pods giant_seed -}`, - true, - "rong value for pods", - -1, - 0, - defaultResyncPeriod, - "", - podModeVerified, - false, - nil, - }, - // fallthrough invalid - { - `kubernetes coredns.local { - fallthrough junk -}`, - true, - "rong argument count", - -1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - // Valid upstream - { - `kubernetes coredns.local { - upstream 13.14.15.16:53 -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - []string{"13.14.15.16:53"}, - }, - // Invalid upstream - { - `kubernetes coredns.local { - upstream 13.14.15.16orange -}`, - true, - "not an IP address or file: \"13.14.15.16orange\"", - -1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - k8sController, opts, err := kubernetesParse(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error, but did not find error for input '%s'. Error was: '%v'", i, test.input, err) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - continue - } - - if test.shouldErr && (len(test.expectedErrContent) < 1) { - t.Fatalf("Test %d: Test marked as expecting an error, but no expectedErrContent provided for input '%s'. Error was: '%v'", i, test.input, err) - } - - if test.shouldErr && (test.expectedZoneCount >= 0) { - t.Errorf("Test %d: Test marked as expecting an error, but provides value for expectedZoneCount!=-1 for input '%s'. Error was: '%v'", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - continue - } - - // No error was raised, so validate initialization of k8sController - // Zones - foundZoneCount := len(k8sController.Zones) - if foundZoneCount != test.expectedZoneCount { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d zones, instead found %d zones: '%v' for input '%s'", i, test.expectedZoneCount, foundZoneCount, k8sController.Zones, test.input) - } - - // Namespaces - foundNSCount := len(k8sController.Namespaces) - if foundNSCount != test.expectedNSCount { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d namespaces. Instead found %d namespaces: '%v' for input '%s'", i, test.expectedNSCount, foundNSCount, k8sController.Namespaces, test.input) - } - - // ResyncPeriod - foundResyncPeriod := opts.resyncPeriod - if foundResyncPeriod != test.expectedResyncPeriod { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with resync period '%s'. Instead found period '%s' for input '%s'", i, test.expectedResyncPeriod, foundResyncPeriod, test.input) - } - - // Labels - if opts.labelSelector != nil { - foundLabelSelectorString := unversioned.FormatLabelSelector(opts.labelSelector) - if foundLabelSelectorString != test.expectedLabelSelector { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with label selector '%s'. Instead found selector '%s' for input '%s'", i, test.expectedLabelSelector, foundLabelSelectorString, test.input) - } - } - // Pods - foundPodMode := k8sController.podMode - if foundPodMode != test.expectedPodMode { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with pod mode '%s'. Instead found pod mode '%s' for input '%s'", i, test.expectedPodMode, foundPodMode, test.input) - } - - // fallthrough - foundFallthrough := k8sController.Fallthrough - if foundFallthrough != test.expectedFallthrough { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, foundFallthrough, test.input) - } - // upstream - foundUpstreams := k8sController.Proxy.Upstreams - if test.expectedUpstreams == nil { - if foundUpstreams != nil { - t.Errorf("Test %d: Expected kubernetes controller to not be initialized with upstreams for input '%s'", i, test.input) - } - } else { - if foundUpstreams == nil { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstreams for input '%s'", i, test.input) - } else { - if len(*foundUpstreams) != len(test.expectedUpstreams) { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d upstreams. Instead found %d upstreams for input '%s'", i, len(test.expectedUpstreams), len(*foundUpstreams), test.input) - } - for j, want := range test.expectedUpstreams { - got := (*foundUpstreams)[j].Select().Name - if got != want { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstream '%s'. Instead found upstream '%s' for input '%s'", i, want, got, test.input) - } - } - - } - } - } -} diff --git a/middleware/kubernetes/setup_ttl_test.go b/middleware/kubernetes/setup_ttl_test.go deleted file mode 100644 index d58f91576..000000000 --- a/middleware/kubernetes/setup_ttl_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestKubernetesParseTTL(t *testing.T) { - tests := []struct { - input string // Corefile data as string - expectedTTL uint32 // expected count of defined zones. - shouldErr bool - }{ - {`kubernetes cluster.local { - ttl 56 - }`, 56, false}, - {`kubernetes cluster.local`, defaultTTL, false}, - {`kubernetes cluster.local { - ttl -1 - }`, 0, true}, - {`kubernetes cluster.local { - ttl 3601 - }`, 0, true}, - } - - for i, tc := range tests { - c := caddy.NewTestController("dns", tc.input) - k, _, err := kubernetesParse(c) - if err != nil && !tc.shouldErr { - t.Fatalf("Test %d: Expected no error, got %q", i, err) - } - if err == nil && tc.shouldErr { - t.Fatalf("Test %d: Expected error, got none", i) - } - if err != nil && tc.shouldErr { - // input should error - continue - } - - if k.ttl != tc.expectedTTL { - t.Errorf("Test %d: Expected TTl to be %d, got %d", i, tc.expectedTTL, k.ttl) - } - } -} |