Start working on tests for auto discovery
This commit is contained in:
		| @ -19,7 +19,6 @@ kubernetes: | ||||
|   cluster-mode: "out" | ||||
|   auto-discover: true | ||||
|   excluded-service-suffixes: | ||||
|     - primary | ||||
|     - canary | ||||
|   service-template: | ||||
|     interval: 30s | ||||
|  | ||||
| @ -2,9 +2,13 @@ package config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/TwinProduction/gatus/core" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/TwinProduction/gatus/core" | ||||
| 	"github.com/TwinProduction/gatus/k8stest" | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| ) | ||||
|  | ||||
| func TestGetBeforeConfigIsLoaded(t *testing.T) { | ||||
| @ -311,3 +315,84 @@ services: | ||||
| 		t.Errorf("config.Security.Basic.PasswordSha512Hash should've been %s, but was %s", expectedPasswordHash, config.Security.Basic.PasswordSha512Hash) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestParseAndValidateConfigBytesWithNoServicesOrAutoDiscovery(t *testing.T) { | ||||
| 	_, err := parseAndValidateConfigBytes([]byte(``)) | ||||
| 	if err != ErrNoServiceInConfig { | ||||
| 		t.Error("The error returned should have been of type ErrNoServiceInConfig") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestParseAndValidateConfigBytesWithKubernetesAutoDiscovery(t *testing.T) { | ||||
| 	var kubernetesServices []v1.Service | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-1", "default")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-2", "default")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-2-canary", "default")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-3", "kube-system")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-4", "tools")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-5", "tools")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-6", "tools")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-7", "metrics")) | ||||
| 	kubernetesServices = append(kubernetesServices, k8stest.CreateTestServices("service-7-canary", "metrics")) | ||||
| 	k8stest.InitializeMockedKubernetesClient(kubernetesServices) | ||||
| 	config, err := parseAndValidateConfigBytes([]byte(` | ||||
| debug: true | ||||
|  | ||||
| kubernetes: | ||||
|   cluster-mode: "mock" | ||||
|   auto-discover: true | ||||
|   excluded-service-suffixes: | ||||
|     - canary | ||||
|   service-template: | ||||
|     interval: 29s | ||||
|     conditions: | ||||
|       - "[STATUS] == 200" | ||||
|   namespaces: | ||||
|     - name: default | ||||
|       hostname-suffix: ".default.svc.cluster.local" | ||||
|       target-path: "/health" | ||||
|     - name: tools | ||||
|       hostname-suffix: ".tools.svc.cluster.local" | ||||
|       target-path: "/health" | ||||
|       excluded-services: | ||||
|         - service-6 | ||||
|     - name: metrics | ||||
|       hostname-suffix: ".metrics.svc.cluster.local" | ||||
|       target-path: "/health" | ||||
| `)) | ||||
| 	if err != nil { | ||||
| 		t.Error("No error should've been returned") | ||||
| 	} | ||||
| 	if config == nil { | ||||
| 		t.Fatal("Config shouldn't have been nil") | ||||
| 	} | ||||
| 	if config.Kubernetes == nil { | ||||
| 		t.Fatal("Kuberbetes config shouldn't have been nil") | ||||
| 	} | ||||
| 	if len(config.Services) != 5 { | ||||
| 		t.Error("Expected 5 services to have been added through k8s auto discovery, got", len(config.Services)) | ||||
| 	} | ||||
| 	for _, service := range config.Services { | ||||
| 		if service.Name == "service-2-canary" || service.Name == "service-7-canary" { | ||||
| 			t.Errorf("service '%s' should've been excluded because excluded-service-suffixes has 'canary'", service.Name) | ||||
| 		} else if service.Name == "service-6" { | ||||
| 			t.Errorf("service '%s' should've been excluded because excluded-services has 'service-6'", service.Name) | ||||
| 		} else if service.Name == "service-3" { | ||||
| 			t.Errorf("service '%s' should've been excluded because the namespace 'kube-system' is not configured for auto discovery", service.Name) | ||||
| 		} else { | ||||
| 			if service.Interval != 29*time.Second { | ||||
| 				t.Errorf("service '%s' should've had an interval of 29s, because the template is configured for it", service.Name) | ||||
| 			} | ||||
| 			if len(service.Conditions) != 1 { | ||||
| 				t.Errorf("service '%s' should've had 1 condition", service.Name) | ||||
| 			} | ||||
| 			if len(service.Conditions) == 1 && *service.Conditions[0] != "[STATUS] == 200" { | ||||
| 				t.Errorf("service '%s' should've had the condition '[STATUS] == 200', because the template is configured for it", service.Name) | ||||
| 			} | ||||
| 			if !strings.HasSuffix(service.URL, ".svc.cluster.local/health") { | ||||
| 				t.Errorf("service '%s' should've had an URL with the suffix '.svc.cluster.local/health'", service.Name) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -6,28 +6,64 @@ import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/TwinProduction/gatus/k8stest" | ||||
| 	"k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/client-go/kubernetes" | ||||
| 	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" | ||||
| 	"k8s.io/client-go/rest" | ||||
| 	"k8s.io/client-go/tools/clientcmd" | ||||
| ) | ||||
|  | ||||
| // KubernetesClientApi is a minimal interface for interacting with Kubernetes | ||||
| // Created mostly to make mocking the Kubernetes client easier | ||||
| type KubernetesClientApi interface { | ||||
| 	GetServices(namespace string) ([]v1.Service, error) | ||||
| } | ||||
|  | ||||
| // KubernetesClient is a working implementation of KubernetesClientApi | ||||
| type KubernetesClient struct { | ||||
| 	client *kubernetes.Clientset | ||||
| } | ||||
|  | ||||
| // GetServices returns a list of services for a given namespace | ||||
| func (k *KubernetesClient) GetServices(namespace string) ([]v1.Service, error) { | ||||
| 	services, err := k.client.CoreV1().Services(namespace).List(metav1.ListOptions{}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return services.Items, nil | ||||
| } | ||||
|  | ||||
| // NewKubernetesClient creates a KubernetesClient | ||||
| func NewKubernetesClient(client *kubernetes.Clientset) *KubernetesClient { | ||||
| 	return &KubernetesClient{ | ||||
| 		client: client, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewClient creates a Kubernetes client for the given ClusterMode | ||||
| func NewClient(clusterMode ClusterMode) (*kubernetes.Clientset, error) { | ||||
| func NewClient(clusterMode ClusterMode) (KubernetesClientApi, error) { | ||||
| 	var kubeConfig *rest.Config | ||||
| 	var err error | ||||
| 	switch clusterMode { | ||||
| 	case ClusterModeIn: | ||||
| 		kubeConfig, err = getInClusterConfig() | ||||
| 		kubeConfig, err = rest.InClusterConfig() | ||||
| 	case ClusterModeOut: | ||||
| 		kubeConfig, err = getOutClusterConfig() | ||||
| 	case ClusterModeMock: | ||||
| 		return k8stest.GetMockedKubernetesClient(), nil | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("invalid cluster mode, try '%s' or '%s'", ClusterModeIn, ClusterModeOut) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("unable to get cluster config for mode '%s': %s", clusterMode, err.Error()) | ||||
| 	} | ||||
| 	return kubernetes.NewForConfig(kubeConfig) | ||||
| 	client, err := kubernetes.NewForConfig(kubeConfig) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return NewKubernetesClient(client), nil | ||||
| } | ||||
|  | ||||
| func homeDir() string { | ||||
| @ -47,7 +83,3 @@ func getOutClusterConfig() (*rest.Config, error) { | ||||
| 	flag.Parse() | ||||
| 	return clientcmd.BuildConfigFromFlags("", *kubeConfig) | ||||
| } | ||||
|  | ||||
| func getInClusterConfig() (*rest.Config, error) { | ||||
| 	return rest.InClusterConfig() | ||||
| } | ||||
|  | ||||
| @ -41,4 +41,5 @@ type ClusterMode string | ||||
| const ( | ||||
| 	ClusterModeIn   ClusterMode = "in" | ||||
| 	ClusterModeOut  ClusterMode = "out" | ||||
| 	ClusterModeMock ClusterMode = "mock" | ||||
| ) | ||||
|  | ||||
							
								
								
									
										14
									
								
								k8s/k8s.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								k8s/k8s.go
									
									
									
									
									
								
							| @ -1,16 +1,10 @@ | ||||
| package k8s | ||||
|  | ||||
| import ( | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/client-go/kubernetes" | ||||
| 	"k8s.io/api/core/v1" | ||||
| ) | ||||
|  | ||||
| // GetKubernetesServices return List of Services from given namespace | ||||
| func GetKubernetesServices(client *kubernetes.Clientset, ns string) ([]corev1.Service, error) { | ||||
| 	services, err := client.CoreV1().Services(ns).List(metav1.ListOptions{}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return services.Items, nil | ||||
| // GetKubernetesServices return a list of Services from the given namespace | ||||
| func GetKubernetesServices(client KubernetesClientApi, namespace string) ([]v1.Service, error) { | ||||
| 	return client.GetServices(namespace) | ||||
| } | ||||
|  | ||||
							
								
								
									
										53
									
								
								k8stest/k8stest.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								k8stest/k8stest.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| package k8stest | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	mockedKubernetesClient *MockKubernetesClient | ||||
| ) | ||||
|  | ||||
| // MockKubernetesClient is a mocked implementation of k8s.KubernetesClientApi | ||||
| type MockKubernetesClient struct { | ||||
| 	Services []v1.Service | ||||
| } | ||||
|  | ||||
| // GetServices returns a list of services in a given namespace | ||||
| func (mock *MockKubernetesClient) GetServices(namespace string) ([]v1.Service, error) { | ||||
| 	var services []v1.Service | ||||
| 	for _, service := range mock.Services { | ||||
| 		if service.Namespace == namespace { | ||||
| 			services = append(services, service) | ||||
| 		} | ||||
| 	} | ||||
| 	return services, nil | ||||
| } | ||||
|  | ||||
| // GetMockedKubernetesClient returns a mocked implementation of k8s.KubernetesClientApi | ||||
| func GetMockedKubernetesClient() *MockKubernetesClient { | ||||
| 	if mockedKubernetesClient != nil { | ||||
| 		return mockedKubernetesClient | ||||
| 	} | ||||
| 	InitializeMockedKubernetesClient(nil) | ||||
| 	return mockedKubernetesClient | ||||
| } | ||||
|  | ||||
| // InitializeMockedKubernetesClient initializes a MockKubernetesClient with a given list of services | ||||
| func InitializeMockedKubernetesClient(services []v1.Service) { | ||||
| 	mockedKubernetesClient = &MockKubernetesClient{ | ||||
| 		Services: services, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateTestServices creates a mocked service for testing purposes | ||||
| func CreateTestServices(name, namespace string) v1.Service { | ||||
| 	return v1.Service{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      name, | ||||
| 			Namespace: namespace, | ||||
| 		}, | ||||
| 		Spec: v1.ServiceSpec{}, | ||||
| 	} | ||||
| } | ||||
| @ -2,12 +2,13 @@ package metric | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/TwinProduction/gatus/config" | ||||
| 	"github.com/TwinProduction/gatus/core" | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	"github.com/prometheus/client_golang/prometheus/promauto" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
|  | ||||
		Reference in New Issue
	
	Block a user