Adding resyncperiod to Corefile (#205)

* Removing old unused inline k8s API code and tests.
* Adding parsing implementation for `resyncperiod` keyword from Corefile.
* Adding tests for parsing `resyncperiod` keyword from Corefile.
8 Updating README.md and conf/k8sCorefile.
This commit is contained in:
Michael Richmond 2016-08-08 14:30:04 -07:00 committed by GitHub
parent 51eaefc037
commit c079de65b5
9 changed files with 123 additions and 965 deletions

View file

@ -2,6 +2,9 @@
.:53 {
# use kubernetes middleware for domain "coredns.local"
kubernetes coredns.local {
# Kubernetes data API resync period
# Example values: 60s, 5m, 1h
resyncperiod 5m
# Use url for k8s API endpoint
endpoint http://localhost:8080
# Assemble k8s record names with the template
@ -11,7 +14,5 @@
}
# Perform DNS response caching for the coredns.local zone
# Cache timeout is provided by the integer in seconds
# This works for the kubernetes middleware.)
#cache 20 coredns.local
#cache 160 coredns.local
#cache 180 coredns.local
}

View file

@ -2,6 +2,7 @@ package setup
import (
"errors"
"fmt"
"log"
"strings"
"time"
@ -27,7 +28,6 @@ func Kubernetes(c *Controller) (middleware.Middleware, error) {
if err != nil {
return nil, err
}
log.Printf("[debug] after parse and start KubeCache, APIconn is: %v", kubernetes.APIConn)
return func(next middleware.Handler) middleware.Handler {
kubernetes.Next = next
@ -51,7 +51,6 @@ func kubernetesParse(c *Controller) (kubernetes.Kubernetes, error) {
if c.Val() == "kubernetes" {
zones := c.RemainingArgs()
log.Printf("[debug] Zones: %v", zones)
if len(zones) == 0 {
k8s.Zones = c.ServerBlockHosts
log.Printf("[debug] Zones(from ServerBlockHosts): %v", zones)
@ -97,6 +96,19 @@ func kubernetesParse(c *Controller) (kubernetes.Kubernetes, error) {
log.Printf("[debug] 'endpoint' keyword provided without any endpoint url value.")
return kubernetes.Kubernetes{}, c.ArgErr()
}
case "resyncperiod":
args := c.RemainingArgs()
if len(args) != 0 {
k8s.ResyncPeriod, err = time.ParseDuration(args[0])
if err != nil {
err = errors.New(fmt.Sprintf("Unable to parse resync duration value. Value provided was '%v'. Example valid values: '15s', '5m', '1h'. Error was: %v", args[0], err))
log.Printf("[ERROR] %v", err)
return kubernetes.Kubernetes{}, err
}
} else {
log.Printf("[debug] 'resyncperiod' keyword provided without any duration value.")
return kubernetes.Kubernetes{}, c.ArgErr()
}
}
}
return k8s, nil

View file

@ -3,17 +3,19 @@ package setup
import (
"strings"
"testing"
"time"
)
func TestKubernetesParse(t *testing.T) {
tests := []struct {
description string
input string
shouldErr bool
description string // Human-facing description of test case
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.
expectedNTValid bool // NameTemplate to be initialized and valid
expectedNSCount int // expected count of namespaces.
expectedResyncPeriod time.Duration // expected resync period value
}{
// positive
{
@ -24,6 +26,7 @@ func TestKubernetesParse(t *testing.T) {
1,
true,
0,
defaultResyncPeriod,
},
{
"kubernetes keyword with multiple zones",
@ -33,6 +36,7 @@ func TestKubernetesParse(t *testing.T) {
2,
true,
0,
defaultResyncPeriod,
},
{
"kubernetes keyword with zone and empty braces",
@ -43,6 +47,7 @@ func TestKubernetesParse(t *testing.T) {
1,
true,
0,
defaultResyncPeriod,
},
{
"endpoint keyword with url",
@ -54,6 +59,7 @@ func TestKubernetesParse(t *testing.T) {
1,
true,
0,
defaultResyncPeriod,
},
{
"template keyword with valid template",
@ -65,6 +71,7 @@ func TestKubernetesParse(t *testing.T) {
1,
true,
0,
defaultResyncPeriod,
},
{
"namespaces keyword with one namespace",
@ -76,6 +83,7 @@ func TestKubernetesParse(t *testing.T) {
1,
true,
1,
defaultResyncPeriod,
},
{
"namespaces keyword with multiple namespaces",
@ -87,10 +95,36 @@ func TestKubernetesParse(t *testing.T) {
1,
true,
2,
defaultResyncPeriod,
},
{
"resync period in seconds",
`kubernetes coredns.local {
resyncperiod 30s
}`,
false,
"",
1,
true,
0,
30 * time.Second,
},
{
"resync period in minutes",
`kubernetes coredns.local {
resyncperiod 15m
}`,
false,
"",
1,
true,
0,
15 * time.Minute,
},
{
"fully specified valid config",
`kubernetes coredns.local test.local {
resyncperiod 15m
endpoint http://localhost:8080
template {service}.{namespace}.{zone}
namespaces demo test
@ -100,6 +134,7 @@ func TestKubernetesParse(t *testing.T) {
2,
true,
2,
15 * time.Minute,
},
// negative
{
@ -110,6 +145,7 @@ func TestKubernetesParse(t *testing.T) {
-1,
false,
-1,
defaultResyncPeriod,
},
{
"kubernetes keyword without a zone",
@ -119,6 +155,7 @@ func TestKubernetesParse(t *testing.T) {
-1,
true,
0,
defaultResyncPeriod,
},
{
"endpoint keyword without an endpoint value",
@ -130,6 +167,7 @@ func TestKubernetesParse(t *testing.T) {
-1,
true,
-1,
defaultResyncPeriod,
},
{
"template keyword without a template value",
@ -141,6 +179,7 @@ func TestKubernetesParse(t *testing.T) {
-1,
false,
0,
defaultResyncPeriod,
},
{
"template keyword with an invalid template value",
@ -152,6 +191,7 @@ func TestKubernetesParse(t *testing.T) {
-1,
false,
0,
defaultResyncPeriod,
},
{
"namespace keyword without a namespace value",
@ -163,6 +203,43 @@ func TestKubernetesParse(t *testing.T) {
-1,
true,
-1,
defaultResyncPeriod,
},
{
"resyncperiod keyword without a duration value",
`kubernetes coredns.local {
resyncperiod
}`,
true,
"Wrong argument count or unexpected line ending after 'resyncperiod'",
-1,
true,
0,
0 * time.Minute,
},
{
"resync period no units",
`kubernetes coredns.local {
resyncperiod 15
}`,
true,
"Unable to parse resync duration value. Value provided was ",
-1,
true,
0,
0 * time.Second,
},
{
"resync period invalid",
`kubernetes coredns.local {
resyncperiod abc
}`,
true,
"Unable to parse resync duration value. Value provided was ",
-1,
true,
0,
0 * time.Second,
},
}
@ -218,8 +295,12 @@ func TestKubernetesParse(t *testing.T) {
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)
t.Logf("k8sController is: %v", k8sController)
t.Logf("k8sController.Namespaces is: %v", k8sController.Namespaces)
}
// ResyncPeriod
foundResyncPeriod := k8sController.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'", test.expectedResyncPeriod, foundResyncPeriod, test.input)
}
}
}

View file

@ -35,6 +35,9 @@ This is the default kubernetes setup, with everything specified in full:
.:53 {
# use kubernetes middleware for domain "coredns.local"
kubernetes coredns.local {
# Kubernetes data API resync period
# Example values: 60s, 5m, 1h
resyncperiod 5m
# Use url for k8s API endpoint
endpoint http://localhost:8080
# Assemble k8s record names with the template
@ -42,10 +45,17 @@ This is the default kubernetes setup, with everything specified in full:
# Only expose the k8s namespace "demo"
namespaces demo
}
# cache 160 coredns.local
# Perform DNS response caching for the coredns.local zone
# Cache timeout is provided by the integer in seconds
#cache 180 coredns.local
}
~~~
Notes:
* If the `namespaces` keyword is omitted, all kubernetes namespaces are exposed.
* If the `template` keyword is omitted, the default template of "{service}.{namespace}.{zone}" is used.
* If the `resyncperiod` keyword is omitted, the default resync period is 5 minutes.
### Basic Setup
#### Launch Kubernetes
@ -305,14 +315,9 @@ TBD:
* Performance
* Improve lookup to reduce size of query result obtained from k8s API.
(namespace-based?, other ideas?)
* Caching/notification of k8s API dataset. (See aledbf fork for
implementation ideas.)
* DNS response caching is good, but we should also cache at the http query
level as well. (Take a look at https://github.com/patrickmn/go-cache as
a potential expiring cache implementation for the http API queries.)
* Additional features:
* Reverse IN-ADDR entries for services. (Is there any value in supporting
reverse lookup records?)
reverse lookup records?) (need tests, functionality should work based on @aledbf's code.)
* How to support label specification in Corefile to allow use of labels to
indicate zone? (Is this even useful?) For example, the following
configuration exposes all services labeled for the "staging" environment
@ -333,14 +338,6 @@ TBD:
flattening to lower case and mapping of non-DNS characters to DNS characters
in a standard way.)
* Expose arbitrary kubernetes repository data as TXT records?
* (done) ~~Support custom user-provided templates for k8s names. A string provided
in the middleware configuration like `{service}.{namespace}.{type}` defines
the template of how to construct record names for the zone. This example
would produce `myservice.mynamespace.svc.cluster.local`. (Basic template
implemented. Need to slice zone out of current template implementation.)~~
* (done) ~~Implement namespace filtering to different zones. That is, zone "a.b"
publishes services from namespace "foo", and zone "x.y" publishes services
from namespaces "bar" and "baz". (Basic version implemented -- need test cases.)~~
* 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?
@ -352,7 +349,3 @@ TBD:
pre-loaded k8s API cache. With and without CoreDNS response caching.
* Try to get rid of kubernetes launch scripts by moving operations into
.travis.yml file.
* ~~Implement test cases for http data parsing using dependency injection
for http get operations.~~
* ~~Automate integration testing with kubernetes. (k8s launch and service
start-up automation is in middleware/kubernetes/tests)~~

View file

@ -129,7 +129,7 @@ func (dns *dnsController) Stop() error {
// Run starts the controller.
func (dns *dnsController) Run() {
log.Println("[debug] starting coredns controller")
log.Println("[debug] Starting k8s notification controllers")
go dns.endpController.Run(dns.stopCh)
go dns.svcController.Run(dns.stopCh)

View file

@ -1,113 +0,0 @@
package k8sclient
import (
"encoding/json"
"net/http"
)
// getK8sAPIResponse wraps the http.Get(url) function to provide dependency
// injection for unit testing.
var getK8sAPIResponse = func(url string) (resp *http.Response, err error) {
resp, err = http.Get(url)
return resp, err
}
func parseJson(url string, target interface{}) error {
r, err := getK8sAPIResponse(url)
if err != nil {
return err
}
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(target)
}
// Kubernetes Resource List
type ResourceList struct {
Kind string `json:"kind"`
GroupVersion string `json:"groupVersion"`
Resources []resource `json:"resources"`
}
type resource struct {
Name string `json:"name"`
Namespaced bool `json:"namespaced"`
Kind string `json:"kind"`
}
// Kubernetes NamespaceList
type NamespaceList struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Metadata apiListMetadata `json:"metadata"`
Items []nsItems `json:"items"`
}
type apiListMetadata struct {
SelfLink string `json:"selfLink"`
ResourceVersion string `json:"resourceVersion"`
}
type nsItems struct {
Metadata nsMetadata `json:"metadata"`
Spec nsSpec `json:"spec"`
Status nsStatus `json:"status"`
}
type nsMetadata struct {
Name string `json:"name"`
SelfLink string `json:"selfLink"`
Uid string `json:"uid"`
ResourceVersion string `json:"resourceVersion"`
CreationTimestamp string `json:"creationTimestamp"`
}
type nsSpec struct {
Finalizers []string `json:"finalizers"`
}
type nsStatus struct {
Phase string `json:"phase"`
}
// Kubernetes ServiceList
type ServiceList struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Metadata apiListMetadata `json:"metadata"`
Items []ServiceItem `json:"items"`
}
type ServiceItem struct {
Metadata serviceMetadata `json:"metadata"`
Spec serviceSpec `json:"spec"`
// Status serviceStatus `json:"status"`
}
type serviceMetadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
SelfLink string `json:"selfLink"`
Uid string `json:"uid"`
ResourceVersion string `json:"resourceVersion"`
CreationTimestamp string `json:"creationTimestamp"`
// labels
}
type serviceSpec struct {
Ports []servicePort `json:"ports"`
ClusterIP string `json:"clusterIP"`
Type string `json:"type"`
SessionAffinity string `json:"sessionAffinity"`
}
type servicePort struct {
Name string `json:"name"`
Protocol string `json:"protocol"`
Port int `json:"port"`
TargetPort int `json:"targetPort"`
}
type serviceStatus struct {
LoadBalancer string `json:"loadBalancer"`
}

View file

@ -1,133 +0,0 @@
package k8sclient
import (
"errors"
"log"
"net/url"
"strings"
)
// API strings
const (
apiBase = "/api/v1"
apiNamespaces = "/namespaces"
apiServices = "/services"
)
// Defaults
const (
defaultBaseURL = "http://localhost:8080"
)
type K8sConnector struct {
baseURL string
}
func (c *K8sConnector) SetBaseURL(u string) error {
url, error := url.Parse(u)
if error != nil {
return error
}
if !url.IsAbs() {
return errors.New("k8sclient: Kubernetes endpoint url must be an absolute URL")
}
c.baseURL = url.String()
return nil
}
func (c *K8sConnector) GetBaseURL() string {
return c.baseURL
}
// URL constructor separated from code to support dependency injection
// for unit tests.
var makeURL = func(parts []string) string {
return strings.Join(parts, "")
}
func (c *K8sConnector) GetResourceList() (*ResourceList, error) {
resources := new(ResourceList)
url := makeURL([]string{c.baseURL, apiBase})
err := parseJson(url, resources)
// TODO: handle no response from k8s
if err != nil {
log.Printf("[ERROR] Response from kubernetes API for GetResourceList() is: %v\n", err)
return nil, err
}
return resources, nil
}
func (c *K8sConnector) GetNamespaceList() (*NamespaceList, error) {
namespaces := new(NamespaceList)
url := makeURL([]string{c.baseURL, apiBase, apiNamespaces})
err := parseJson(url, namespaces)
if err != nil {
log.Printf("[ERROR] Response from kubernetes API for GetNamespaceList() is: %v\n", err)
return nil, err
}
return namespaces, nil
}
func (c *K8sConnector) GetServiceList() (*ServiceList, error) {
services := new(ServiceList)
url := makeURL([]string{c.baseURL, apiBase, apiServices})
err := parseJson(url, services)
// TODO: handle no response from k8s
if err != nil {
log.Printf("[ERROR] Response from kubernetes API for GetServiceList() is: %v\n", err)
return nil, err
}
return services, nil
}
// GetServicesByNamespace returns a map of
// namespacename :: [ kubernetesServiceItem ]
func (c *K8sConnector) GetServicesByNamespace() (map[string][]ServiceItem, error) {
items := make(map[string][]ServiceItem)
k8sServiceList, err := c.GetServiceList()
if err != nil {
log.Printf("[ERROR] Getting service list produced error: %v", err)
return nil, err
}
// TODO: handle no response from k8s
if k8sServiceList == nil {
return nil, nil
}
k8sItemList := k8sServiceList.Items
for _, i := range k8sItemList {
namespace := i.Metadata.Namespace
items[namespace] = append(items[namespace], i)
}
return items, nil
}
func NewK8sConnector(baseURL string) *K8sConnector {
k := new(K8sConnector)
if baseURL == "" {
baseURL = defaultBaseURL
}
err := k.SetBaseURL(baseURL)
if err != nil {
return nil
}
return k
}

View file

@ -1,680 +0,0 @@
package k8sclient
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
var validURLs = []string{
"http://www.github.com",
"http://www.github.com:8080",
"http://8.8.8.8",
"http://8.8.8.8:9090",
"www.github.com:8080",
}
var invalidURLs = []string{
"www.github.com",
"8.8.8.8",
"8.8.8.8:1010",
"8.8`8.8",
}
func TestNewK8sConnector(t *testing.T) {
var conn *K8sConnector
var url string
// Create with empty URL
conn = nil
url = ""
conn = NewK8sConnector("")
if conn == nil {
t.Errorf("Expected K8sConnector instance. Instead got '%v'", conn)
}
url = conn.GetBaseURL()
if url != defaultBaseURL {
t.Errorf("Expected K8sConnector instance to be initialized with defaultBaseURL. Instead got '%v'", url)
}
// Create with valid URL
for _, validURL := range validURLs {
conn = nil
url = ""
conn = NewK8sConnector(validURL)
if conn == nil {
t.Errorf("Expected K8sConnector instance. Instead got '%v'", conn)
}
url = conn.GetBaseURL()
if url != validURL {
t.Errorf("Expected K8sConnector instance to be initialized with supplied url '%v'. Instead got '%v'", validURL, url)
}
}
// Create with invalid URL
for _, invalidURL := range invalidURLs {
conn = nil
url = ""
conn = NewK8sConnector(invalidURL)
if conn != nil {
t.Errorf("Expected to not get K8sConnector instance. Instead got '%v'", conn)
continue
}
}
}
func TestSetBaseURL(t *testing.T) {
// SetBaseURL with valid URLs should work...
for _, validURL := range validURLs {
conn := NewK8sConnector(defaultBaseURL)
err := conn.SetBaseURL(validURL)
if err != nil {
t.Errorf("Expected to receive nil, instead got error '%v'", err)
continue
}
url := conn.GetBaseURL()
if url != validURL {
t.Errorf("Expected to connector url to be set to value '%v', instead set to '%v'", validURL, url)
continue
}
}
// SetBaseURL with invalid or non absolute URLs should not change state...
for _, invalidURL := range invalidURLs {
conn := NewK8sConnector(defaultBaseURL)
originalURL := conn.GetBaseURL()
err := conn.SetBaseURL(invalidURL)
if err == nil {
t.Errorf("Expected to receive an error value, instead got nil")
}
url := conn.GetBaseURL()
if url != originalURL {
t.Errorf("Expected base url to not change, instead it changed to '%v'", url)
}
}
}
func TestGetNamespaceList(t *testing.T) {
// Set up a test http server
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, namespaceListJsonData)
}))
defer testServer.Close()
// Overwrite URL constructor to access testServer
makeURL = func(parts []string) string {
return testServer.URL
}
expectedNamespaces := []string{"default", "demo", "test"}
apiConn := NewK8sConnector("")
namespaceList, err := apiConn.GetNamespaceList()
if err != nil {
t.Errorf("Expected no error from from GetNamespaceList(), instead got %v", err)
}
if namespaceList == nil {
t.Errorf("Expected data from GetNamespaceList(), instead got nil")
}
kind := namespaceList.Kind
if kind != "NamespaceList" {
t.Errorf("Expected data from GetNamespaceList() to have Kind='NamespaceList', instead got Kind='%v'", kind)
}
// Ensure correct number of namespaces found
expectedCount := len(expectedNamespaces)
namespaceCount := len(namespaceList.Items)
if namespaceCount != expectedCount {
t.Errorf("Expected '%v' namespaces from GetNamespaceList(), instead found '%v' namespaces", expectedCount, namespaceCount)
}
// Check that all expectedNamespaces are found in the parsed data
for _, ns := range expectedNamespaces {
found := false
for _, item := range namespaceList.Items {
if item.Metadata.Name == ns {
found = true
break
}
}
if !found {
t.Errorf("Expected '%v' namespace is not in the parsed data from GetServicesByNamespace()", ns)
}
}
}
func TestGetServiceList(t *testing.T) {
// Set up a test http server
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, serviceListJsonData)
}))
defer testServer.Close()
// Overwrite URL constructor to access testServer
makeURL = func(parts []string) string {
return testServer.URL
}
expectedServices := []string{"kubernetes", "mynginx", "mywebserver"}
apiConn := NewK8sConnector("")
serviceList, err := apiConn.GetServiceList()
if err != nil {
t.Errorf("Expected no error from from GetNamespaceList(), instead got %v", err)
}
if serviceList == nil {
t.Errorf("Expected data from GetServiceList(), instead got nil")
}
kind := serviceList.Kind
if kind != "ServiceList" {
t.Errorf("Expected data from GetServiceList() to have Kind='ServiceList', instead got Kind='%v'", kind)
}
// Ensure correct number of services found
expectedCount := len(expectedServices)
serviceCount := len(serviceList.Items)
if serviceCount != expectedCount {
t.Errorf("Expected '%v' services from GetServiceList(), instead found '%v' services", expectedCount, serviceCount)
}
// Check that all expectedServices are found in the parsed data
for _, s := range expectedServices {
found := false
for _, item := range serviceList.Items {
if item.Metadata.Name == s {
found = true
break
}
}
if !found {
t.Errorf("Expected '%v' service is not in the parsed data from GetServiceList()", s)
}
}
}
func TestGetServicesByNamespace(t *testing.T) {
// Set up a test http server
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, serviceListJsonData)
}))
defer testServer.Close()
// Overwrite URL constructor to access testServer
makeURL = func(parts []string) string {
return testServer.URL
}
expectedNamespaces := []string{"default", "demo"}
apiConn := NewK8sConnector("")
servicesByNamespace, err := apiConn.GetServicesByNamespace()
if err != nil {
t.Errorf("Expected no error from from GetServicesByNamespace(), instead got %v", err)
}
// Ensure correct number of namespaces found
expectedCount := len(expectedNamespaces)
namespaceCount := len(servicesByNamespace)
if namespaceCount != expectedCount {
t.Errorf("Expected '%v' namespaces from GetServicesByNamespace(), instead found '%v' namespaces", expectedCount, namespaceCount)
}
// Check that all expectedNamespaces are found in the parsed data
for _, ns := range expectedNamespaces {
_, ok := servicesByNamespace[ns]
if !ok {
t.Errorf("Expected '%v' namespace is not in the parsed data from GetServicesByNamespace()", ns)
}
}
}
func TestGetResourceList(t *testing.T) {
// Set up a test http server
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, resourceListJsonData)
}))
defer testServer.Close()
// Overwrite URL constructor to access testServer
makeURL = func(parts []string) string {
return testServer.URL
}
expectedResources := []string{"bindings",
"componentstatuses",
"configmaps",
"endpoints",
"events",
"limitranges",
"namespaces",
"namespaces/finalize",
"namespaces/status",
"nodes",
"nodes/proxy",
"nodes/status",
"persistentvolumeclaims",
"persistentvolumeclaims/status",
"persistentvolumes",
"persistentvolumes/status",
"pods",
"pods/attach",
"pods/binding",
"pods/exec",
"pods/log",
"pods/portforward",
"pods/proxy",
"pods/status",
"podtemplates",
"replicationcontrollers",
"replicationcontrollers/scale",
"replicationcontrollers/status",
"resourcequotas",
"resourcequotas/status",
"secrets",
"serviceaccounts",
"services",
"services/proxy",
"services/status",
}
apiConn := NewK8sConnector("")
resourceList, err := apiConn.GetResourceList()
if err != nil {
t.Errorf("Expected no error from from GetResourceList(), instead got %v", err)
}
if resourceList == nil {
t.Errorf("Expected data from GetResourceList(), instead got nil")
}
kind := resourceList.Kind
if kind != "APIResourceList" {
t.Errorf("Expected data from GetResourceList() to have Kind='ResourceList', instead got Kind='%v'", kind)
}
// Ensure correct number of resources found
expectedCount := len(expectedResources)
resourceCount := len(resourceList.Resources)
if resourceCount != expectedCount {
t.Errorf("Expected '%v' resources from GetResourceList(), instead found '%v' resources", expectedCount, resourceCount)
}
// Check that all expectedResources are found in the parsed data
for _, r := range expectedResources {
found := false
for _, item := range resourceList.Resources {
if item.Name == r {
found = true
break
}
}
if !found {
t.Errorf("Expected '%v' resource is not in the parsed data from GetResourceList()", r)
}
}
}
// Sample namespace data for kubernetes with 3 namespaces:
// "default", "demo", and "test".
const namespaceListJsonData string = `{
"kind": "NamespaceList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/",
"resourceVersion": "121279"
},
"items": [
{
"metadata": {
"name": "default",
"selfLink": "/api/v1/namespaces/default",
"uid": "fb1c92d1-2f39-11e6-b9db-0800279930f6",
"resourceVersion": "6",
"creationTimestamp": "2016-06-10T18:34:35Z"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
},
{
"metadata": {
"name": "demo",
"selfLink": "/api/v1/namespaces/demo",
"uid": "73be8ffd-2f3a-11e6-b9db-0800279930f6",
"resourceVersion": "111",
"creationTimestamp": "2016-06-10T18:37:57Z"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
},
{
"metadata": {
"name": "test",
"selfLink": "/api/v1/namespaces/test",
"uid": "c0be05fa-3352-11e6-b9db-0800279930f6",
"resourceVersion": "121276",
"creationTimestamp": "2016-06-15T23:41:59Z"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
]
}`
// Sample service data for kubernetes with 3 services:
// * "kubernetes" (in "default" namespace)
// * "mynginx" (in "demo" namespace)
// * "webserver" (in "demo" namespace)
const serviceListJsonData string = `
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/services",
"resourceVersion": "147965"
},
"items": [
{
"metadata": {
"name": "kubernetes",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/services/kubernetes",
"uid": "fb1cb0d3-2f39-11e6-b9db-0800279930f6",
"resourceVersion": "7",
"creationTimestamp": "2016-06-10T18:34:35Z",
"labels": {
"component": "apiserver",
"provider": "kubernetes"
}
},
"spec": {
"ports": [
{
"name": "https",
"protocol": "TCP",
"port": 443,
"targetPort": 443
}
],
"clusterIP": "10.0.0.1",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"metadata": {
"name": "mynginx",
"namespace": "demo",
"selfLink": "/api/v1/namespaces/demo/services/mynginx",
"uid": "93c117ac-2f3a-11e6-b9db-0800279930f6",
"resourceVersion": "147",
"creationTimestamp": "2016-06-10T18:38:51Z",
"labels": {
"run": "mynginx"
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 80,
"targetPort": 80
}
],
"selector": {
"run": "mynginx"
},
"clusterIP": "10.0.0.132",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"metadata": {
"name": "mywebserver",
"namespace": "demo",
"selfLink": "/api/v1/namespaces/demo/services/mywebserver",
"uid": "aed62187-33e5-11e6-a224-0800279930f6",
"resourceVersion": "138185",
"creationTimestamp": "2016-06-16T17:13:45Z",
"labels": {
"run": "mywebserver"
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 443,
"targetPort": 443
}
],
"selector": {
"run": "mywebserver"
},
"clusterIP": "10.0.0.63",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}
]
}
`
// Sample resource data for kubernetes.
const resourceListJsonData string = `{
"kind": "APIResourceList",
"groupVersion": "v1",
"resources": [
{
"name": "bindings",
"namespaced": true,
"kind": "Binding"
},
{
"name": "componentstatuses",
"namespaced": false,
"kind": "ComponentStatus"
},
{
"name": "configmaps",
"namespaced": true,
"kind": "ConfigMap"
},
{
"name": "endpoints",
"namespaced": true,
"kind": "Endpoints"
},
{
"name": "events",
"namespaced": true,
"kind": "Event"
},
{
"name": "limitranges",
"namespaced": true,
"kind": "LimitRange"
},
{
"name": "namespaces",
"namespaced": false,
"kind": "Namespace"
},
{
"name": "namespaces/finalize",
"namespaced": false,
"kind": "Namespace"
},
{
"name": "namespaces/status",
"namespaced": false,
"kind": "Namespace"
},
{
"name": "nodes",
"namespaced": false,
"kind": "Node"
},
{
"name": "nodes/proxy",
"namespaced": false,
"kind": "Node"
},
{
"name": "nodes/status",
"namespaced": false,
"kind": "Node"
},
{
"name": "persistentvolumeclaims",
"namespaced": true,
"kind": "PersistentVolumeClaim"
},
{
"name": "persistentvolumeclaims/status",
"namespaced": true,
"kind": "PersistentVolumeClaim"
},
{
"name": "persistentvolumes",
"namespaced": false,
"kind": "PersistentVolume"
},
{
"name": "persistentvolumes/status",
"namespaced": false,
"kind": "PersistentVolume"
},
{
"name": "pods",
"namespaced": true,
"kind": "Pod"
},
{
"name": "pods/attach",
"namespaced": true,
"kind": "Pod"
},
{
"name": "pods/binding",
"namespaced": true,
"kind": "Binding"
},
{
"name": "pods/exec",
"namespaced": true,
"kind": "Pod"
},
{
"name": "pods/log",
"namespaced": true,
"kind": "Pod"
},
{
"name": "pods/portforward",
"namespaced": true,
"kind": "Pod"
},
{
"name": "pods/proxy",
"namespaced": true,
"kind": "Pod"
},
{
"name": "pods/status",
"namespaced": true,
"kind": "Pod"
},
{
"name": "podtemplates",
"namespaced": true,
"kind": "PodTemplate"
},
{
"name": "replicationcontrollers",
"namespaced": true,
"kind": "ReplicationController"
},
{
"name": "replicationcontrollers/scale",
"namespaced": true,
"kind": "Scale"
},
{
"name": "replicationcontrollers/status",
"namespaced": true,
"kind": "ReplicationController"
},
{
"name": "resourcequotas",
"namespaced": true,
"kind": "ResourceQuota"
},
{
"name": "resourcequotas/status",
"namespaced": true,
"kind": "ResourceQuota"
},
{
"name": "secrets",
"namespaced": true,
"kind": "Secret"
},
{
"name": "serviceaccounts",
"namespaced": true,
"kind": "ServiceAccount"
},
{
"name": "services",
"namespaced": true,
"kind": "Service"
},
{
"name": "services/proxy",
"namespaced": true,
"kind": "Service"
},
{
"name": "services/status",
"namespaced": true,
"kind": "Service"
}
]
}`

View file

@ -20,10 +20,6 @@ import (
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
const (
defaultResyncPeriod = 5 * time.Minute
)
type Kubernetes struct {
Next middleware.Handler
Zones []string
@ -37,7 +33,7 @@ type Kubernetes struct {
func (g *Kubernetes) StartKubeCache() error {
// For a custom api server or running outside a k8s cluster
// set URL in env.KUBERNETES_MASTER
// set URL in env.KUBERNETES_MASTER or set endpoint in Corefile
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
overrides := &clientcmd.ConfigOverrides{}
if len(g.APIEndpoint) > 0 {
@ -55,6 +51,7 @@ func (g *Kubernetes) StartKubeCache() error {
log.Printf("[ERROR] Failed to create kubernetes notification controller: %v", err)
return err
}
log.Printf("[debug] Starting kubernetes middleware with k8s API resync period: %s", g.ResyncPeriod)
g.APIConn = newdnsController(kubeClient, g.ResyncPeriod)
go g.APIConn.Run()