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 {
|
.:53 {
|
||||||
# use kubernetes middleware for domain "coredns.local"
|
# use kubernetes middleware for domain "coredns.local"
|
||||||
kubernetes coredns.local {
|
kubernetes coredns.local {
|
||||||
|
# Kubernetes data API resync period
|
||||||
|
# Example values: 60s, 5m, 1h
|
||||||
|
resyncperiod 5m
|
||||||
# Use url for k8s API endpoint
|
# Use url for k8s API endpoint
|
||||||
endpoint http://localhost:8080
|
endpoint http://localhost:8080
|
||||||
# Assemble k8s record names with the template
|
# Assemble k8s record names with the template
|
||||||
|
@ -11,7 +14,5 @@
|
||||||
}
|
}
|
||||||
# Perform DNS response caching for the coredns.local zone
|
# Perform DNS response caching for the coredns.local zone
|
||||||
# Cache timeout is provided by the integer in seconds
|
# Cache timeout is provided by the integer in seconds
|
||||||
# This works for the kubernetes middleware.)
|
#cache 180 coredns.local
|
||||||
#cache 20 coredns.local
|
|
||||||
#cache 160 coredns.local
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -27,7 +28,6 @@ func Kubernetes(c *Controller) (middleware.Middleware, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Printf("[debug] after parse and start KubeCache, APIconn is: %v", kubernetes.APIConn)
|
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
kubernetes.Next = next
|
kubernetes.Next = next
|
||||||
|
@ -51,7 +51,6 @@ func kubernetesParse(c *Controller) (kubernetes.Kubernetes, error) {
|
||||||
if c.Val() == "kubernetes" {
|
if c.Val() == "kubernetes" {
|
||||||
zones := c.RemainingArgs()
|
zones := c.RemainingArgs()
|
||||||
|
|
||||||
log.Printf("[debug] Zones: %v", zones)
|
|
||||||
if len(zones) == 0 {
|
if len(zones) == 0 {
|
||||||
k8s.Zones = c.ServerBlockHosts
|
k8s.Zones = c.ServerBlockHosts
|
||||||
log.Printf("[debug] Zones(from ServerBlockHosts): %v", zones)
|
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.")
|
log.Printf("[debug] 'endpoint' keyword provided without any endpoint url value.")
|
||||||
return kubernetes.Kubernetes{}, c.ArgErr()
|
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
|
return k8s, nil
|
||||||
|
|
|
@ -3,17 +3,19 @@ package setup
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKubernetesParse(t *testing.T) {
|
func TestKubernetesParse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
description string
|
description string // Human-facing description of test case
|
||||||
input string
|
input string // Corefile data as string
|
||||||
shouldErr bool
|
shouldErr bool // true if test case is exected to produce an error.
|
||||||
expectedErrContent string // substring from the expected error. Empty for positive cases.
|
expectedErrContent string // substring from the expected error. Empty for positive cases.
|
||||||
expectedZoneCount int // expected count of defined zones.
|
expectedZoneCount int // expected count of defined zones.
|
||||||
expectedNTValid bool // NameTemplate to be initialized and valid
|
expectedNTValid bool // NameTemplate to be initialized and valid
|
||||||
expectedNSCount int // expected count of namespaces.
|
expectedNSCount int // expected count of namespaces.
|
||||||
|
expectedResyncPeriod time.Duration // expected resync period value
|
||||||
}{
|
}{
|
||||||
// positive
|
// positive
|
||||||
{
|
{
|
||||||
|
@ -24,6 +26,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with multiple zones",
|
"kubernetes keyword with multiple zones",
|
||||||
|
@ -33,6 +36,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
2,
|
2,
|
||||||
true,
|
true,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with zone and empty braces",
|
"kubernetes keyword with zone and empty braces",
|
||||||
|
@ -43,6 +47,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword with url",
|
"endpoint keyword with url",
|
||||||
|
@ -54,6 +59,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"template keyword with valid template",
|
"template keyword with valid template",
|
||||||
|
@ -65,6 +71,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with one namespace",
|
"namespaces keyword with one namespace",
|
||||||
|
@ -76,6 +83,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
1,
|
1,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with multiple namespaces",
|
"namespaces keyword with multiple namespaces",
|
||||||
|
@ -87,10 +95,36 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
2,
|
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",
|
"fully specified valid config",
|
||||||
`kubernetes coredns.local test.local {
|
`kubernetes coredns.local test.local {
|
||||||
|
resyncperiod 15m
|
||||||
endpoint http://localhost:8080
|
endpoint http://localhost:8080
|
||||||
template {service}.{namespace}.{zone}
|
template {service}.{namespace}.{zone}
|
||||||
namespaces demo test
|
namespaces demo test
|
||||||
|
@ -100,6 +134,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
2,
|
2,
|
||||||
true,
|
true,
|
||||||
2,
|
2,
|
||||||
|
15 * time.Minute,
|
||||||
},
|
},
|
||||||
// negative
|
// negative
|
||||||
{
|
{
|
||||||
|
@ -110,6 +145,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
-1,
|
-1,
|
||||||
false,
|
false,
|
||||||
-1,
|
-1,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword without a zone",
|
"kubernetes keyword without a zone",
|
||||||
|
@ -119,6 +155,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
-1,
|
-1,
|
||||||
true,
|
true,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword without an endpoint value",
|
"endpoint keyword without an endpoint value",
|
||||||
|
@ -130,6 +167,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
-1,
|
-1,
|
||||||
true,
|
true,
|
||||||
-1,
|
-1,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"template keyword without a template value",
|
"template keyword without a template value",
|
||||||
|
@ -141,6 +179,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
-1,
|
-1,
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"template keyword with an invalid template value",
|
"template keyword with an invalid template value",
|
||||||
|
@ -152,6 +191,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
-1,
|
-1,
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespace keyword without a namespace value",
|
"namespace keyword without a namespace value",
|
||||||
|
@ -163,6 +203,43 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
-1,
|
-1,
|
||||||
true,
|
true,
|
||||||
-1,
|
-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)
|
foundNSCount := len(k8sController.Namespaces)
|
||||||
if foundNSCount != test.expectedNSCount {
|
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.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 {
|
.:53 {
|
||||||
# use kubernetes middleware for domain "coredns.local"
|
# use kubernetes middleware for domain "coredns.local"
|
||||||
kubernetes coredns.local {
|
kubernetes coredns.local {
|
||||||
|
# Kubernetes data API resync period
|
||||||
|
# Example values: 60s, 5m, 1h
|
||||||
|
resyncperiod 5m
|
||||||
# Use url for k8s API endpoint
|
# Use url for k8s API endpoint
|
||||||
endpoint http://localhost:8080
|
endpoint http://localhost:8080
|
||||||
# Assemble k8s record names with the template
|
# 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"
|
# Only expose the k8s namespace "demo"
|
||||||
namespaces 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
|
### Basic Setup
|
||||||
|
|
||||||
#### Launch Kubernetes
|
#### Launch Kubernetes
|
||||||
|
@ -305,14 +315,9 @@ TBD:
|
||||||
* Performance
|
* Performance
|
||||||
* Improve lookup to reduce size of query result obtained from k8s API.
|
* Improve lookup to reduce size of query result obtained from k8s API.
|
||||||
(namespace-based?, other ideas?)
|
(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:
|
* Additional features:
|
||||||
* Reverse IN-ADDR entries for services. (Is there any value in supporting
|
* 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
|
* How to support label specification in Corefile to allow use of labels to
|
||||||
indicate zone? (Is this even useful?) For example, the following
|
indicate zone? (Is this even useful?) For example, the following
|
||||||
configuration exposes all services labeled for the "staging" environment
|
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
|
flattening to lower case and mapping of non-DNS characters to DNS characters
|
||||||
in a standard way.)
|
in a standard way.)
|
||||||
* Expose arbitrary kubernetes repository data as TXT records?
|
* 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
|
* DNS Correctness
|
||||||
* Do we need to generate synthetic zone records for namespaces?
|
* Do we need to generate synthetic zone records for namespaces?
|
||||||
* Do we need to generate synthetic zone records for the skydns synthetic zones?
|
* 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.
|
pre-loaded k8s API cache. With and without CoreDNS response caching.
|
||||||
* Try to get rid of kubernetes launch scripts by moving operations into
|
* Try to get rid of kubernetes launch scripts by moving operations into
|
||||||
.travis.yml file.
|
.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.
|
// Run starts the controller.
|
||||||
func (dns *dnsController) Run() {
|
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.endpController.Run(dns.stopCh)
|
||||||
go dns.svcController.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"
|
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultResyncPeriod = 5 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
type Kubernetes struct {
|
type Kubernetes struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
Zones []string
|
Zones []string
|
||||||
|
@ -37,7 +33,7 @@ type Kubernetes struct {
|
||||||
|
|
||||||
func (g *Kubernetes) StartKubeCache() error {
|
func (g *Kubernetes) StartKubeCache() error {
|
||||||
// For a custom api server or running outside a k8s cluster
|
// 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()
|
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||||
overrides := &clientcmd.ConfigOverrides{}
|
overrides := &clientcmd.ConfigOverrides{}
|
||||||
if len(g.APIEndpoint) > 0 {
|
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)
|
log.Printf("[ERROR] Failed to create kubernetes notification controller: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Printf("[debug] Starting kubernetes middleware with k8s API resync period: %s", g.ResyncPeriod)
|
||||||
g.APIConn = newdnsController(kubeClient, g.ResyncPeriod)
|
g.APIConn = newdnsController(kubeClient, g.ResyncPeriod)
|
||||||
|
|
||||||
go g.APIConn.Run()
|
go g.APIConn.Run()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue