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:
parent
51eaefc037
commit
c079de65b5
9 changed files with 123 additions and 965 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,17 +3,19 @@ package setup
|
|||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestKubernetesParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
shouldErr bool
|
||||
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.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)~~
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}`
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue