diff --git a/controllers/depresolver/depresolver.go b/controllers/depresolver/depresolver.go index e0fcdecd65..374cb05d9b 100644 --- a/controllers/depresolver/depresolver.go +++ b/controllers/depresolver/depresolver.go @@ -65,6 +65,8 @@ type Config struct { EdgeDNSZone string // DNSZone controlled by gslb; e.g. cloud.example.com DNSZone string + // K8gbNamespace k8gb namespace + K8gbNamespace string // Infoblox configuration Infoblox Infoblox // Override the behavior of GSLB in the test environments diff --git a/controllers/depresolver/depresolver_config.go b/controllers/depresolver/depresolver_config.go index f5cc49ceef..29f87c182b 100644 --- a/controllers/depresolver/depresolver_config.go +++ b/controllers/depresolver/depresolver_config.go @@ -24,6 +24,7 @@ const ( InfobloxPasswordKey = "EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD" OverrideWithFakeDNSKey = "OVERRIDE_WITH_FAKE_EXT_DNS" OverrideFakeInfobloxKey = "FAKE_INFOBLOX" + K8gbNamespaceKey = "POD_NAMESPACE" ) // ResolveOperatorConfig executes once. It reads operator's configuration @@ -39,6 +40,7 @@ func (dr *DependencyResolver) ResolveOperatorConfig() (*Config, error) { dr.config.EdgeDNSServer = env.GetEnvAsStringOrFallback(EdgeDNSServerKey, "") dr.config.EdgeDNSZone = env.GetEnvAsStringOrFallback(EdgeDNSZoneKey, "") dr.config.DNSZone = env.GetEnvAsStringOrFallback(DNSZoneKey, "") + dr.config.K8gbNamespace = env.GetEnvAsStringOrFallback(K8gbNamespaceKey, "") dr.config.Infoblox.Host = env.GetEnvAsStringOrFallback(InfobloxGridHostKey, "") dr.config.Infoblox.Version = env.GetEnvAsStringOrFallback(InfobloxVersionKey, "") dr.config.Infoblox.Port, _ = env.GetEnvAsIntOrFallback(InfobloxPortKey, 0) @@ -53,6 +55,10 @@ func (dr *DependencyResolver) ResolveOperatorConfig() (*Config, error) { } func (dr *DependencyResolver) validateConfig(config *Config) (err error) { + err = field("k8gbNamespace", config.K8gbNamespace).isNotEmpty().matchRegexp(k8sNamespaceRegex).err + if err != nil { + return err + } err = field("reconcileRequeueSeconds", config.ReconcileRequeueSeconds).isHigherThanZero().err if err != nil { return err diff --git a/controllers/depresolver/depresolver_test.go b/controllers/depresolver/depresolver_test.go index 6b1d7e1857..d8232e966f 100644 --- a/controllers/depresolver/depresolver_test.go +++ b/controllers/depresolver/depresolver_test.go @@ -28,6 +28,7 @@ var predefinedConfig = Config{ EdgeDNSServer: "cloud.example.com", EdgeDNSZone: "8.8.8.8", DNSZone: "example.com", + K8gbNamespace: "k8gb", Infoblox: Infoblox{ "Infoblox.host.com", "0.0.3", @@ -466,6 +467,37 @@ func TestResolveConfigWithoutDnsZone(t *testing.T) { arrangeVariablesAndAssert(t, expected, assert.Error, DNSZoneKey) } +func TestResolveConfigWithEmptyK8gbNamespace(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.K8gbNamespace = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, K8gbNamespaceKey) +} + +func TestResolveConfigWithInvalidK8gbNamespace(t *testing.T) { + // arrange + defer cleanup() + for _, ns := range []string{"-", "Op.", "kube/netes", "my-ns???", "123-MY", "MY-123"} { + expected := predefinedConfig + expected.K8gbNamespace = ns + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) + } +} + +func TestResolveConfigWithValidK8gbNamespace(t *testing.T) { + // arrange + defer cleanup() + for _, ns := range []string{"k8gb", "my-123", "123-my", "n"} { + expected := predefinedConfig + expected.K8gbNamespace = ns + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) + } +} + func TestResolveEmptyExtGeoTags(t *testing.T) { // arrange defer cleanup() @@ -928,7 +960,7 @@ func arrangeVariablesAndAssert(t *testing.T, expected Config, func cleanup() { for _, s := range []string{ReconcileRequeueSecondsKey, ClusterGeoTagKey, ExtClustersGeoTagsKey, EdgeDNSZoneKey, DNSZoneKey, EdgeDNSServerKey, Route53EnabledKey, InfobloxGridHostKey, InfobloxVersionKey, InfobloxPortKey, InfobloxUsernameKey, InfobloxPasswordKey, - OverrideWithFakeDNSKey, OverrideFakeInfobloxKey} { + OverrideWithFakeDNSKey, OverrideFakeInfobloxKey, K8gbNamespaceKey} { if os.Unsetenv(s) != nil { panic(fmt.Errorf("cleanup %s", s)) } @@ -942,6 +974,7 @@ func configureEnvVar(config Config) { _ = os.Setenv(EdgeDNSServerKey, config.EdgeDNSServer) _ = os.Setenv(EdgeDNSZoneKey, config.EdgeDNSZone) _ = os.Setenv(DNSZoneKey, config.DNSZone) + _ = os.Setenv(K8gbNamespaceKey, config.K8gbNamespace) _ = os.Setenv(Route53EnabledKey, strconv.FormatBool(config.route53Enabled)) _ = os.Setenv(NS1EnabledKey, strconv.FormatBool(config.ns1Enabled)) _ = os.Setenv(InfobloxGridHostKey, config.Infoblox.Host) diff --git a/controllers/depresolver/depresolver_validator.go b/controllers/depresolver/depresolver_validator.go index b4d922dd0e..04fa0e1b44 100644 --- a/controllers/depresolver/depresolver_validator.go +++ b/controllers/depresolver/depresolver_validator.go @@ -15,6 +15,8 @@ const ( ipAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" // versionNumberRegex matches version in formats 0.1.2, v0.1.2, v0.1.2-alpha versionNumberRegex = "^(v){0,1}(0|(?:[1-9]\\d*))(?:\\.(0|(?:[1-9]\\d*))(?:\\.(0|(?:[1-9]\\d*)))?(?:\\-([\\w][\\w\\.\\-_]*))?)?$" + // k8sNamespaceRegex matches valid kubernetes namespace + k8sNamespaceRegex = "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" ) // validator wrapper against field to be verified diff --git a/controllers/dnsupdate.go b/controllers/dnsupdate.go index 1792de42fd..66ebf35215 100644 --- a/controllers/dnsupdate.go +++ b/controllers/dnsupdate.go @@ -373,7 +373,7 @@ func Dig(edgeDNSServer, fqdn string) ([]string, error) { func (r *GslbReconciler) coreDNSExposedIPs() ([]string, error) { coreDNSService := &corev1.Service{} - err := r.Get(context.TODO(), types.NamespacedName{Namespace: k8gbNamespace, Name: coreDNSExtServiceName}, coreDNSService) + err := r.Get(context.TODO(), types.NamespacedName{Namespace: r.Config.K8gbNamespace, Name: coreDNSExtServiceName}, coreDNSService) if err != nil { if errors.IsNotFound(err) { log.Info(fmt.Sprintf("Can't find %s service", coreDNSExtServiceName)) @@ -411,7 +411,7 @@ func (r *GslbReconciler) createZoneDelegationRecordsForExternalDNS(gslb *k8gbv1b NSRecord := &externaldns.DNSEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("k8gb-ns-%s", dnsProvider), - Namespace: k8gbNamespace, + Namespace: r.Config.K8gbNamespace, Annotations: map[string]string{"k8gb.absa.oss/dnstype": dnsProvider}, }, Spec: externaldns.DNSEndpointSpec{ @@ -431,7 +431,7 @@ func (r *GslbReconciler) createZoneDelegationRecordsForExternalDNS(gslb *k8gbv1b }, }, } - res, err := r.ensureDNSEndpoint(k8gbNamespace, NSRecord) + res, err := r.ensureDNSEndpoint(r.Config.K8gbNamespace, NSRecord) if err != nil { return res, err } diff --git a/controllers/finalize.go b/controllers/finalize.go index 97eef434de..c081f04fe9 100644 --- a/controllers/finalize.go +++ b/controllers/finalize.go @@ -20,7 +20,7 @@ func (r *GslbReconciler) finalizeGslb(gslb *k8gbv1beta1.Gslb) error { if r.Config.EdgeDNSType == depresolver.DNSTypeRoute53 { log.Info("Removing Zone Delegation entries...") dnsEndpointRoute53 := &externaldns.DNSEndpoint{} - err := r.Get(context.Background(), client.ObjectKey{Namespace: k8gbNamespace, Name: "k8gb-ns-route53"}, dnsEndpointRoute53) + err := r.Get(context.Background(), client.ObjectKey{Namespace: r.Config.K8gbNamespace, Name: "k8gb-ns-route53"}, dnsEndpointRoute53) if err != nil { if errors.IsNotFound(err) { log.Info(fmt.Sprint(err)) diff --git a/controllers/gslb_controller.go b/controllers/gslb_controller.go index 881eba6ea0..efb1601a46 100644 --- a/controllers/gslb_controller.go +++ b/controllers/gslb_controller.go @@ -19,9 +19,10 @@ package controllers import ( "context" "fmt" - "os" "time" + "github.com/AbsaOSS/k8gb/controllers/metrics" + "github.com/AbsaOSS/k8gb/controllers/depresolver" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -41,7 +42,6 @@ import ( k8gbv1beta1 "github.com/AbsaOSS/k8gb/api/v1beta1" ) -var k8gbNamespace = os.Getenv("POD_NAMESPACE") var log = logf.Log.WithName("controller_gslb") // GslbReconciler reconciles a Gslb object @@ -51,6 +51,7 @@ type GslbReconciler struct { Scheme *runtime.Scheme Config *depresolver.Config DepResolver *depresolver.DependencyResolver + Metrics *metrics.PrometheusMetrics } const ( diff --git a/controllers/gslb_controller_test.go b/controllers/gslb_controller_test.go index 76b9654ad7..d3d12c6259 100644 --- a/controllers/gslb_controller_test.go +++ b/controllers/gslb_controller_test.go @@ -10,6 +10,11 @@ import ( "testing" "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + + "github.com/AbsaOSS/k8gb/controllers/metrics" + "github.com/stretchr/testify/require" ibclient "github.com/infobloxopen/infoblox-go-client" @@ -19,8 +24,6 @@ import ( k8gbv1beta1 "github.com/AbsaOSS/k8gb/api/v1beta1" "github.com/AbsaOSS/k8gb/controllers/depresolver" "github.com/AbsaOSS/k8gb/controllers/internal/utils" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" corev1 "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -56,6 +59,7 @@ var predefinedConfig = depresolver.Config{ EdgeDNSServer: "8.8.8.8", EdgeDNSZone: "example.com", DNSZone: "cloud.example.com", + K8gbNamespace: "k8gb", Infoblox: depresolver.Infoblox{ Host: "fakeinfoblox.example.com", Username: "foo", @@ -119,9 +123,13 @@ func TestIngressHostsPerStatusMetric(t *testing.T) { // arrange defer cleanup() settings := provideSettings(t, predefinedConfig) + err := settings.reconciler.Metrics.Register() + require.NoError(t, err) + defer settings.reconciler.Metrics.Unregister() expectedHostsMetricCount := 3 // act - err := settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) + ingressHostsPerStatusMetric := settings.reconciler.Metrics.GetIngressHostsPerStatusMetric() + err = settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) actualHostsMetricCount := testutil.CollectAndCount(ingressHostsPerStatusMetric) // assert assert.NoError(t, err, "Failed to get expected gslb") @@ -136,15 +144,19 @@ func TestIngressHostsPerStatusMetricReflectionForHealthyStatus(t *testing.T) { func() { // arrange settings := provideSettings(t, predefinedConfig) + defer settings.reconciler.Metrics.Unregister() + err := settings.reconciler.Metrics.Register() + require.NoError(t, err) serviceName := "frontend-podinfo" defer deleteHealthyService(t, &settings, serviceName) expectedHostsMetric := 1. createHealthyService(t, &settings, serviceName) reconcileAndUpdateGslb(t, settings) // act - err := settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) - healthyHosts := - ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": settings.gslb.Namespace, "name": settings.gslb.Name, "status": healthyStatus}) + err = settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) + ingressHostsPerStatusMetric := settings.reconciler.Metrics.GetIngressHostsPerStatusMetric() + healthyHosts := ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": settings.gslb.Namespace, + "name": settings.gslb.Name, "status": metrics.HealthyStatus}) actualHostsMetric := testutil.ToFloat64(healthyHosts) // assert assert.NoError(t, err, "Failed to get expected gslb") @@ -158,11 +170,15 @@ func TestIngressHostsPerStatusMetricReflectionForUnhealthyStatus(t *testing.T) { // arrange defer cleanup() settings := provideSettings(t, predefinedConfig) - err := settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) + defer settings.reconciler.Metrics.Unregister() + err := settings.reconciler.Metrics.Register() + require.NoError(t, err) + err = settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) expectedHostsMetricCount := 0. // act - unhealthyHosts := - ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": settings.gslb.Namespace, "name": settings.gslb.Name, "status": unhealthyStatus}) + ingressHostsPerStatusMetric := settings.reconciler.Metrics.GetIngressHostsPerStatusMetric() + unhealthyHosts := ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": settings.gslb.Namespace, + "name": settings.gslb.Name, "status": metrics.UnhealthyStatus}) actualHostsMetricCount := testutil.ToFloat64(unhealthyHosts) // assert assert.NoError(t, err, "Failed to get expected gslb") @@ -177,7 +193,8 @@ func TestIngressHostsPerStatusMetricReflectionForUnhealthyStatus(t *testing.T) { expectedHostsMetricCount = 1 // act unhealthyHosts = - ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": settings.gslb.Namespace, "name": settings.gslb.Name, "status": unhealthyStatus}) + ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": settings.gslb.Namespace, + "name": settings.gslb.Name, "status": metrics.UnhealthyStatus}) actualHostsMetricCount = testutil.ToFloat64(unhealthyHosts) // assert assert.Equal(t, expectedHostsMetricCount, actualHostsMetricCount, "expected %v managed hosts with Healthy status, but got %v", @@ -188,6 +205,9 @@ func TestIngressHostsPerStatusMetricReflectionForNotFoundStatus(t *testing.T) { // arrange defer cleanup() settings := provideSettings(t, predefinedConfig) + defer settings.reconciler.Metrics.Unregister() + err := settings.reconciler.Metrics.Register() + require.NoError(t, err) expectedHostsMetricCount := 2.0 serviceName := "unhealthy-app" @@ -196,10 +216,11 @@ func TestIngressHostsPerStatusMetricReflectionForNotFoundStatus(t *testing.T) { deleteUnhealthyService(t, &settings, serviceName) // act - err := settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) + err = settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) require.NoError(t, err, "Failed to get expected gslb") + ingressHostsPerStatusMetric := settings.reconciler.Metrics.GetIngressHostsPerStatusMetric() unknownHosts, err := ingressHostsPerStatusMetric.GetMetricWith( - prometheus.Labels{"namespace": settings.gslb.Namespace, "name": settings.gslb.Name, "status": notFoundStatus}) + prometheus.Labels{"namespace": settings.gslb.Namespace, "name": settings.gslb.Name, "status": metrics.NotFoundStatus}) require.NoError(t, err, "Failed to get ingress metrics") actualHostsMetricCount := testutil.ToFloat64(unknownHosts) // assert @@ -218,7 +239,10 @@ func TestHealthyRecordMetric(t *testing.T) { } serviceName := "frontend-podinfo" settings := provideSettings(t, predefinedConfig) - err := settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) + defer settings.reconciler.Metrics.Unregister() + err := settings.reconciler.Metrics.Register() + require.NoError(t, err) + err = settings.client.Get(context.TODO(), settings.request.NamespacedName, settings.gslb) require.NoError(t, err, "Failed to get expected gslb") defer deleteHealthyService(t, &settings, serviceName) createHealthyService(t, &settings, serviceName) @@ -229,6 +253,7 @@ func TestHealthyRecordMetric(t *testing.T) { require.NoError(t, err, "Failed to update gslb Ingress Address") reconcileAndUpdateGslb(t, settings) // act + healthyRecordsMetric := settings.reconciler.Metrics.GetHealthyRecordsMetric() actualHealthyRecordsMetricCount := testutil.ToFloat64(healthyRecordsMetric) reconcileAndUpdateGslb(t, settings) // assert @@ -238,6 +263,12 @@ func TestHealthyRecordMetric(t *testing.T) { func TestMetricLinterCheck(t *testing.T) { // arrange + settings := provideSettings(t, predefinedConfig) + defer settings.reconciler.Metrics.Unregister() + err := settings.reconciler.Metrics.Register() + require.NoError(t, err) + healthyRecordsMetric := settings.reconciler.Metrics.GetHealthyRecordsMetric() + ingressHostsPerStatusMetric := settings.reconciler.Metrics.GetIngressHostsPerStatusMetric() for name, scenario := range map[string]prometheus.Collector{ "healthy_records": healthyRecordsMetric, "ingress_hosts_per_status": ingressHostsPerStatusMetric, @@ -690,7 +721,7 @@ func TestCreatesNSDNSRecordsForRoute53(t *testing.T) { coreDNSService := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: coreDNSExtServiceName, - Namespace: k8gbNamespace, + Namespace: predefinedConfig.K8gbNamespace, }, } serviceIPs := []corev1.LoadBalancerIngress{ @@ -712,7 +743,7 @@ func TestCreatesNSDNSRecordsForRoute53(t *testing.T) { settings.reconciler.Config = &customConfig reconcileAndUpdateGslb(t, settings) - err = settings.client.Get(context.TODO(), client.ObjectKey{Namespace: k8gbNamespace, Name: "k8gb-ns-route53"}, dnsEndpointRoute53) + err = settings.client.Get(context.TODO(), client.ObjectKey{Namespace: predefinedConfig.K8gbNamespace, Name: "k8gb-ns-route53"}, dnsEndpointRoute53) require.NoError(t, err, "Failed to get expected DNSEndpoint") got := dnsEndpointRoute53.Annotations["k8gb.absa.oss/dnstype"] gotEp := dnsEndpointRoute53.Spec.Endpoints @@ -756,7 +787,7 @@ func TestCreatesNSDNSRecordsForNS1(t *testing.T) { coreDNSService := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: coreDNSExtServiceName, - Namespace: k8gbNamespace, + Namespace: predefinedConfig.K8gbNamespace, }, } serviceIPs := []corev1.LoadBalancerIngress{ @@ -778,7 +809,7 @@ func TestCreatesNSDNSRecordsForNS1(t *testing.T) { settings.reconciler.Config = &customConfig reconcileAndUpdateGslb(t, settings) - err = settings.client.Get(context.TODO(), client.ObjectKey{Namespace: k8gbNamespace, Name: "k8gb-ns-ns1"}, dnsEndpointNS1) + err = settings.client.Get(context.TODO(), client.ObjectKey{Namespace: predefinedConfig.K8gbNamespace, Name: "k8gb-ns-ns1"}, dnsEndpointNS1) require.NoError(t, err, "Failed to get expected DNSEndpoint") got := dnsEndpointNS1.Annotations["k8gb.absa.oss/dnstype"] gotEp := dnsEndpointNS1.Spec.Endpoints @@ -840,7 +871,7 @@ func TestRoute53ZoneDelegationGarbageCollection(t *testing.T) { coreDNSService := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: coreDNSExtServiceName, - Namespace: k8gbNamespace, + Namespace: predefinedConfig.K8gbNamespace, }, } serviceIPs := []corev1.LoadBalancerIngress{ @@ -867,7 +898,7 @@ func TestRoute53ZoneDelegationGarbageCollection(t *testing.T) { // assert dnsEndpointRoute53 := &externaldns.DNSEndpoint{} - err = settings.client.Get(context.TODO(), client.ObjectKey{Namespace: k8gbNamespace, Name: "k8gb-ns-route53"}, dnsEndpointRoute53) + err = settings.client.Get(context.TODO(), client.ObjectKey{Namespace: predefinedConfig.K8gbNamespace, Name: "k8gb-ns-route53"}, dnsEndpointRoute53) require.Error(t, err, "k8gb-ns-route53 DNSEndpoint should be garbage collected") } @@ -1078,6 +1109,7 @@ func provideSettings(t *testing.T, expected depresolver.Config) (settings testSe r.Config = config // Mock request to simulate Reconcile() being called on an event for a // watched resource . + r.Metrics = metrics.NewPrometheusMetrics(*config) req := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: gslb.Name, @@ -1114,7 +1146,7 @@ func provideSettings(t *testing.T, expected depresolver.Config) (settings testSe func cleanup() { for _, s := range []string{depresolver.ReconcileRequeueSecondsKey, depresolver.ClusterGeoTagKey, depresolver.ExtClustersGeoTagsKey, - depresolver.EdgeDNSZoneKey, depresolver.DNSZoneKey, depresolver.EdgeDNSServerKey, + depresolver.EdgeDNSZoneKey, depresolver.DNSZoneKey, depresolver.EdgeDNSServerKey, depresolver.K8gbNamespaceKey, depresolver.Route53EnabledKey, depresolver.InfobloxGridHostKey, depresolver.InfobloxVersionKey, depresolver.InfobloxPortKey, depresolver.InfobloxUsernameKey, depresolver.InfobloxPasswordKey, depresolver.OverrideWithFakeDNSKey, depresolver.OverrideFakeInfobloxKey} { if os.Unsetenv(s) != nil { @@ -1130,6 +1162,7 @@ func configureEnvVar(config depresolver.Config) { _ = os.Setenv(depresolver.EdgeDNSServerKey, config.EdgeDNSServer) _ = os.Setenv(depresolver.EdgeDNSZoneKey, config.EdgeDNSZone) _ = os.Setenv(depresolver.DNSZoneKey, config.DNSZone) + _ = os.Setenv(depresolver.K8gbNamespaceKey, config.K8gbNamespace) _ = os.Setenv(depresolver.Route53EnabledKey, strconv.FormatBool(config.EdgeDNSType == depresolver.DNSTypeRoute53)) _ = os.Setenv(depresolver.InfobloxGridHostKey, config.Infoblox.Host) _ = os.Setenv(depresolver.InfobloxVersionKey, config.Infoblox.Version) diff --git a/controllers/metrics.go b/controllers/metrics.go deleted file mode 100644 index ea2db2eefb..0000000000 --- a/controllers/metrics.go +++ /dev/null @@ -1,72 +0,0 @@ -package controllers - -import ( - k8gbv1beta1 "github.com/AbsaOSS/k8gb/api/v1beta1" - "github.com/prometheus/client_golang/prometheus" - "sigs.k8s.io/controller-runtime/pkg/metrics" -) - -const ( - gslbSubsystem = "gslb" - healthyStatus = "Healthy" - unhealthyStatus = "Unhealthy" - notFoundStatus = "NotFound" -) - -// Custom gslb prometheus metrics -var ( - healthyRecordsMetric = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: k8gbNamespace, - Subsystem: gslbSubsystem, - Name: "healthy_records", - Help: "Number of healthy records observed by K8GB.", - }, - []string{"namespace", "name"}, - ) - ingressHostsPerStatusMetric = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: k8gbNamespace, - Subsystem: gslbSubsystem, - Name: "ingress_hosts_per_status", - Help: "Number of managed hosts observed by K8GB.", - }, - []string{"namespace", "name", "status"}, - ) -) - -func (r *GslbReconciler) updateIngressHostsPerStatusMetric(gslb *k8gbv1beta1.Gslb, serviceHealth map[string]string) error { - var healthyHostsCount, unhealthyHostsCount, notFoundHostsCount int - for _, hs := range serviceHealth { - switch hs { - case healthyStatus: - healthyHostsCount++ - case unhealthyStatus: - unhealthyHostsCount++ - default: - notFoundHostsCount++ - } - } - ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name, "status": healthyStatus}). - Set(float64(healthyHostsCount)) - ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name, "status": unhealthyStatus}). - Set(float64(unhealthyHostsCount)) - ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name, "status": notFoundStatus}). - Set(float64(notFoundHostsCount)) - - return nil -} - -func (r *GslbReconciler) updateHealthyRecordsMetric(gslb *k8gbv1beta1.Gslb, healthyRecords map[string][]string) error { - var hrsCount int - for _, hrs := range healthyRecords { - hrsCount += len(hrs) - } - healthyRecordsMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name}).Set(float64(hrsCount)) - return nil -} - -func init() { - metrics.Registry.MustRegister(healthyRecordsMetric) - metrics.Registry.MustRegister(ingressHostsPerStatusMetric) -} diff --git a/controllers/metrics/prometheus.go b/controllers/metrics/prometheus.go new file mode 100644 index 0000000000..7f13bca701 --- /dev/null +++ b/controllers/metrics/prometheus.go @@ -0,0 +1,113 @@ +package metrics + +import ( + "fmt" + "sync" + + k8gbv1beta1 "github.com/AbsaOSS/k8gb/api/v1beta1" + "github.com/AbsaOSS/k8gb/controllers/depresolver" + "github.com/prometheus/client_golang/prometheus" + crm "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +const ( + gslbSubsystem = "gslb" + HealthyStatus = "Healthy" + UnhealthyStatus = "Unhealthy" + NotFoundStatus = "NotFound" +) + +type PrometheusMetrics struct { + healthyRecordsMetric *prometheus.GaugeVec + ingressHostsPerStatusMetric *prometheus.GaugeVec + once sync.Once +} + +// NewPrometheusMetrics creates new prometheus metrics instance +func NewPrometheusMetrics(config depresolver.Config) (metrics *PrometheusMetrics) { + metrics = new(PrometheusMetrics) + metrics.healthyRecordsMetric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: config.K8gbNamespace, + Subsystem: gslbSubsystem, + Name: "healthy_records", + Help: "Number of healthy records observed by K8GB.", + }, + []string{"namespace", "name"}, + ) + metrics.ingressHostsPerStatusMetric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: config.K8gbNamespace, + Subsystem: gslbSubsystem, + Name: "ingress_hosts_per_status", + Help: "Number of managed hosts observed by K8GB.", + }, + []string{"namespace", "name", "status"}, + ) + return +} + +func (m *PrometheusMetrics) UpdateIngressHostsPerStatusMetric(gslb *k8gbv1beta1.Gslb, serviceHealth map[string]string) error { + var healthyHostsCount, unhealthyHostsCount, notFoundHostsCount int + for _, hs := range serviceHealth { + switch hs { + case HealthyStatus: + healthyHostsCount++ + case UnhealthyStatus: + unhealthyHostsCount++ + default: + notFoundHostsCount++ + } + } + m.ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name, "status": HealthyStatus}). + Set(float64(healthyHostsCount)) + m.ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name, "status": UnhealthyStatus}). + Set(float64(unhealthyHostsCount)) + m.ingressHostsPerStatusMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name, "status": NotFoundStatus}). + Set(float64(notFoundHostsCount)) + return nil +} + +func (m *PrometheusMetrics) UpdateHealthyRecordsMetric(gslb *k8gbv1beta1.Gslb, healthyRecords map[string][]string) error { + var hrsCount int + for _, hrs := range healthyRecords { + hrsCount += len(hrs) + } + m.healthyRecordsMetric.With(prometheus.Labels{"namespace": gslb.Namespace, "name": gslb.Name}).Set(float64(hrsCount)) + return nil +} + +// Register prometheus metrics. Read register documentation, but shortly: +// You can register metric with given name only once +func (m *PrometheusMetrics) Register() (err error) { + m.once.Do(func() { + if err = crm.Registry.Register(m.healthyRecordsMetric); err != nil { + return + } + if err = crm.Registry.Register(m.ingressHostsPerStatusMetric); err != nil { + return + } + }) + if err != nil { + return fmt.Errorf("can't register prometheus metrics: %s", err) + } + return +} + +// Unregister prometheus metrics +func (m *PrometheusMetrics) Unregister() { + crm.Registry.Unregister(m.healthyRecordsMetric) + crm.Registry.Unregister(m.ingressHostsPerStatusMetric) +} + +// GetHealthyRecordsMetric retrieves actual copy of healthy record metric +// TODO: consider to implement concrete metrics as a functions which returns metrics as slices/maps or structures +func (m *PrometheusMetrics) GetHealthyRecordsMetric() prometheus.GaugeVec { + return *m.healthyRecordsMetric +} + +// GetIngressHostsPerStatusMetric retrieves actual copy of ingress host metric +// TODO: consider to implement concrete metrics as a functions which returns metrics as slices/maps or structures +func (m *PrometheusMetrics) GetIngressHostsPerStatusMetric() prometheus.GaugeVec { + return *m.ingressHostsPerStatusMetric +} diff --git a/controllers/status.go b/controllers/status.go index 3a348a8379..9b51cb25bd 100644 --- a/controllers/status.go +++ b/controllers/status.go @@ -20,7 +20,7 @@ func (r *GslbReconciler) updateGslbStatus(gslb *k8gbv1beta1.Gslb) error { return err } - err = r.updateIngressHostsPerStatusMetric(gslb, gslb.Status.ServiceHealth) + err = r.Metrics.UpdateIngressHostsPerStatusMetric(gslb, gslb.Status.ServiceHealth) if err != nil { return err } @@ -32,7 +32,7 @@ func (r *GslbReconciler) updateGslbStatus(gslb *k8gbv1beta1.Gslb) error { gslb.Status.GeoTag = r.Config.ClusterGeoTag - err = r.updateHealthyRecordsMetric(gslb, gslb.Status.HealthyRecords) + err = r.Metrics.UpdateHealthyRecordsMetric(gslb, gslb.Status.HealthyRecords) if err != nil { return err } diff --git a/main.go b/main.go index 6c61514d1a..a9e4e40ff6 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ import ( k8gbv1beta1 "github.com/AbsaOSS/k8gb/api/v1beta1" "github.com/AbsaOSS/k8gb/controllers" + "github.com/AbsaOSS/k8gb/controllers/metrics" externaldns "sigs.k8s.io/external-dns/endpoint" // +kubebuilder:scaffold:imports ) @@ -93,15 +94,22 @@ func main() { if err != nil { setupLog.Error(err, "reading config env variables") } + setupLog.Info("starting metrics") + reconciler.Metrics = metrics.NewPrometheusMetrics(*reconciler.Config) + err = reconciler.Metrics.Register() + if err != nil { + setupLog.Error(err, "register metrics error") + os.Exit(1) + } if err = reconciler.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Gslb") os.Exit(1) } // +kubebuilder:scaffold:builder - setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } + reconciler.Metrics.Unregister() } diff --git a/registry/k3s.yaml b/registry/k3s.yaml deleted file mode 120000 index 4d1545ac2b..0000000000 --- a/registry/k3s.yaml +++ /dev/null @@ -1 +0,0 @@ -/output/kubeconfig.yaml \ No newline at end of file