mirror of
https://github.com/coredns/coredns.git
synced 2025-12-08 19:35:10 -05:00
k8s middleware cleanup, testcases, basic SRV (#181)
* Removing unnecessary gitignore pattern
* Updating Makefile to run unittests for subpackages
* Adding Corefile validation to ignore overlapping zones
* Fixing SRV query handling
* Updating README.md now that SRV works
* Fixing debug message, adding code comment
* Clarifying implementation of zone normalization
* "Overlapping zones" is ill-defined. Reimplemented zone overlap/subzone
checking to contain these functions in k8s middleware and provide
better code comments explaining the normalization.
* Separate build verbosity from test verbosity
* Cleaning up comments to match repo code style
* Merging warning messages into single message
* Moving function docs to before function declaration
* Adding test cases for k8sclient connector
* Tests cover connector create and setting base url
* Fixed bugs in connector create and setting base url functions
* Updaing README to group and order development work
* Priority focused on achieving functional parity with SkyDNS.
* Adding work items to README and cleaning up formatting
* More README format cleaning
* List formating
* Refactoring k8s API call to allow dependency injection
* Add test cases for data parsing from k8s into dataobject structures
* URL is dependency-injected to allow replacement with a mock http
server during test execution
* Adding more data validation for JSON parsing tests
* Adding test case for GetResourceList()
* Adding notes about SkyDNS embedded IP and port record names
* Marked test case implemented.
* Fixing formatting for example command.
* Fixing formatting
* Adding notes about Docker image building.
* Adding SkyDNS work item
* Updating TODO list
* Adding name template to Corefile to specify how k8s record names are assembled
* Adding template support for multi-segment zones
* Updating example CoreFile for k8s with template comment
* Misc whitespace cleanup
* Adding SkyDNS naming notes
* Adding namespace filtering to CoreFile config
* Updating example k8sCoreFile to specify namespaces
* Removing unused codepath
* Adding check for valid namespace
* More README TODO restructuring to focus effort
* Adding template validation while parsing CoreFile
* Record name template is considered invalid if it contains a symbol of the form ${bar} where the symbol
"${bar}" is not an accepted template symbol.
* Refactoring generation of answer records
* Parse typeName out of query string
* Refactor answer record creation as operation over list of ServiceItems
* Moving k8s API caching into SkyDNS equivalency segment
* Adding function to assemble record names from template
* Warning: This commit may be broken. Syncing to get laptop code over to dev machine.
* More todo notes
* Adding comment describing sample test data.
* Update k8sCorefile
* Adding comment
* Adding filtering support for kubernetes "type"
* Required refactoring to support reuse of the StringInSlice function.
* Cleaning up formatting
* Adding note about SkyDNS supporting word "any".
* baseUrl -> baseURL
* Also removed debug statement from core/setup/kubernetes.go
* Fixing test breaking from Url -> URL naming changes
* Changing record name template language ${...} -> {...}
* Fix formatting with go fmt
* Updating all k8sclient data getters to return error value
* Adding error message to k8sclient data accessors
* Cleaning up setup for kubernetes
* Removed verbose nils in initial k8s middleware instance
* Set reasonable defaults if CoreFile has no parameters in the
kubernetes block. (k8s endpoint, and name template)
* Formatting cleanup -- go fmt
This commit is contained in:
committed by
Miek Gieben
parent
558c34a23e
commit
289f53d386
@@ -1,110 +1,113 @@
|
||||
package k8sclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
||||
func getJson(url string, target interface{}) error {
|
||||
r, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
return json.NewDecoder(r.Body).Decode(target)
|
||||
// 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"`
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
SelfLink string `json:"selfLink"`
|
||||
ResourceVersion string `json:"resourceVersion"`
|
||||
}
|
||||
|
||||
type nsItems struct {
|
||||
Metadata nsMetadata `json:"metadata"`
|
||||
Spec nsSpec `json:"spec"`
|
||||
Status nsStatus `json:"status"`
|
||||
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"`
|
||||
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"`
|
||||
Finalizers []string `json:"finalizers"`
|
||||
}
|
||||
|
||||
type nsStatus struct {
|
||||
Phase string `json:"phase"`
|
||||
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"`
|
||||
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"`
|
||||
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
|
||||
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"`
|
||||
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"`
|
||||
Name string `json:"name"`
|
||||
Protocol string `json:"protocol"`
|
||||
Port int `json:"port"`
|
||||
TargetPort int `json:"targetPort"`
|
||||
}
|
||||
|
||||
type serviceStatus struct {
|
||||
LoadBalancer string `json:"loadBalancer"`
|
||||
LoadBalancer string `json:"loadBalancer"`
|
||||
}
|
||||
|
||||
@@ -1,117 +1,157 @@
|
||||
package k8sclient
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
"net/url"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// API strings
|
||||
const (
|
||||
apiBase = "/api/v1"
|
||||
apiNamespaces = "/namespaces"
|
||||
apiServices = "/services"
|
||||
apiBase = "/api/v1"
|
||||
apiNamespaces = "/namespaces"
|
||||
apiServices = "/services"
|
||||
)
|
||||
|
||||
// Defaults
|
||||
const (
|
||||
defaultBaseUrl = "http://localhost:8080"
|
||||
defaultBaseURL = "http://localhost:8080"
|
||||
)
|
||||
|
||||
|
||||
type K8sConnector struct {
|
||||
baseUrl string
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func (c *K8sConnector) SetBaseUrl(u string) error {
|
||||
validUrl, error := url.Parse(u)
|
||||
func (c *K8sConnector) SetBaseURL(u string) error {
|
||||
url, error := url.Parse(u)
|
||||
|
||||
if error != nil {
|
||||
return error
|
||||
}
|
||||
c.baseUrl = validUrl.String()
|
||||
if error != nil {
|
||||
return error
|
||||
}
|
||||
|
||||
return nil
|
||||
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
|
||||
func (c *K8sConnector) GetBaseURL() string {
|
||||
return c.baseURL
|
||||
}
|
||||
|
||||
|
||||
func (c *K8sConnector) GetResourceList() *ResourceList {
|
||||
resources := new(ResourceList)
|
||||
|
||||
error := getJson((c.baseUrl + apiBase), resources)
|
||||
if error != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return resources
|
||||
// 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)
|
||||
|
||||
func (c *K8sConnector) GetNamespaceList() *NamespaceList {
|
||||
namespaces := new(NamespaceList)
|
||||
url := makeURL([]string{c.baseURL, apiBase})
|
||||
err := parseJson(url, resources)
|
||||
// TODO: handle no response from k8s
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] Response from kubernetes API for GetResourceList() is: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
error := getJson((c.baseUrl + apiBase + apiNamespaces), namespaces)
|
||||
if error != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return namespaces
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func (c *K8sConnector) GetNamespaceList() (*NamespaceList, error) {
|
||||
namespaces := new(NamespaceList)
|
||||
|
||||
func (c *K8sConnector) GetServiceList() *ServiceList {
|
||||
services := new(ServiceList)
|
||||
url := makeURL([]string{c.baseURL, apiBase, apiNamespaces})
|
||||
err := parseJson(url, namespaces)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] Response from kubernetes API for GetNamespaceList() is: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
error := getJson((c.baseUrl + apiBase + apiServices), services)
|
||||
if error != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return services
|
||||
return namespaces, nil
|
||||
}
|
||||
|
||||
func (c *K8sConnector) GetServiceList() (*ServiceList, error) {
|
||||
services := new(ServiceList)
|
||||
|
||||
func (c *K8sConnector) GetServicesByNamespace() map[string][]ServiceItem {
|
||||
// GetServicesByNamespace returns a map of namespacename :: [ kubernetesServiceItem ]
|
||||
url := makeURL([]string{c.baseURL, apiBase, apiServices})
|
||||
err := parseJson(url, services)
|
||||
// TODO: handle no response from k8s
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] Response from kubernetes API for GetServiceList() is: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make(map[string][]ServiceItem)
|
||||
|
||||
k8sServiceList := c.GetServiceList()
|
||||
k8sItemList := k8sServiceList.Items
|
||||
|
||||
for _, i := range k8sItemList {
|
||||
namespace := i.Metadata.Namespace
|
||||
items[namespace] = append(items[namespace], i)
|
||||
}
|
||||
|
||||
return items
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// GetServicesByNamespace returns a map of
|
||||
// namespacename :: [ kubernetesServiceItem ]
|
||||
func (c *K8sConnector) GetServicesByNamespace() (map[string][]ServiceItem, error) {
|
||||
|
||||
func (c *K8sConnector) GetServiceItemInNamespace(namespace string, servicename string) *ServiceItem {
|
||||
// GetServiceItemInNamespace returns the ServiceItem that matches servicename in the namespace
|
||||
items := make(map[string][]ServiceItem)
|
||||
|
||||
itemMap := c.GetServicesByNamespace()
|
||||
k8sServiceList, err := c.GetServiceList()
|
||||
|
||||
// TODO: Handle case where namesapce == nil
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] Getting service list produced error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, x := range itemMap[namespace] {
|
||||
if x.Metadata.Name == servicename {
|
||||
return &x
|
||||
}
|
||||
}
|
||||
// TODO: handle no response from k8s
|
||||
if k8sServiceList == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// No matching item found in namespace
|
||||
return nil
|
||||
k8sItemList := k8sServiceList.Items
|
||||
|
||||
for _, i := range k8sItemList {
|
||||
namespace := i.Metadata.Namespace
|
||||
items[namespace] = append(items[namespace], i)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetServiceItemsInNamespace returns the ServiceItems that match
|
||||
// servicename in the namespace
|
||||
func (c *K8sConnector) GetServiceItemsInNamespace(namespace string, servicename string) ([]*ServiceItem, error) {
|
||||
|
||||
func NewK8sConnector(baseurl string) *K8sConnector {
|
||||
k := new(K8sConnector)
|
||||
k.SetBaseUrl(baseurl)
|
||||
itemMap, err := c.GetServicesByNamespace()
|
||||
|
||||
return k
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] Getting service list produced error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Handle case where namespace == nil
|
||||
|
||||
var serviceItems []*ServiceItem
|
||||
|
||||
for _, x := range itemMap[namespace] {
|
||||
if x.Metadata.Name == servicename {
|
||||
serviceItems = append(serviceItems, &x)
|
||||
}
|
||||
}
|
||||
|
||||
return serviceItems, nil
|
||||
}
|
||||
|
||||
func NewK8sConnector(baseURL string) *K8sConnector {
|
||||
k := new(K8sConnector)
|
||||
|
||||
if baseURL == "" {
|
||||
baseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
err := k.SetBaseURL(baseURL)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
680
middleware/kubernetes/k8sclient/k8sclient_test.go
Normal file
680
middleware/kubernetes/k8sclient/k8sclient_test.go
Normal file
@@ -0,0 +1,680 @@
|
||||
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"
|
||||
}
|
||||
]
|
||||
}`
|
||||
Reference in New Issue
Block a user