From c7de08a856d48f70f70cafcd2fe224c63339494d Mon Sep 17 00:00:00 2001 From: Muhammad Faizan Date: Tue, 23 Apr 2024 12:26:15 +0200 Subject: [PATCH] added metrics --- api/v1alpha1/zz_generated.deepcopy.go | 2 +- cmd/main.go | 5 + internal/controller/nats/controller.go | 4 + internal/controller/nats/deprovisioner.go | 4 + internal/controller/nats/provisioner.go | 6 + internal/controller/nats/unit_test.go | 5 + internal/metrics/metrics.go | 90 ++++++++++ internal/metrics/mocks/collector.go | 194 ++++++++++++++++++++++ testutils/integration/integration.go | 6 + 9 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 internal/metrics/metrics.go create mode 100644 internal/metrics/mocks/collector.go diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e5a8d0ba..a1a88095 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1alpha1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/cmd/main.go b/cmd/main.go index c53b86fe..921f61e2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,6 +18,7 @@ package main //nolint:cyclop // main function needs to initialize many objects import ( "flag" + "github.com/kyma-project/nats-manager/internal/metrics" "os" nmapiv1alpha1 "github.com/kyma-project/nats-manager/api/v1alpha1" @@ -150,6 +151,9 @@ func main() { //nolint:funlen // main function needs to initialize many objects natsManager := nmmgr.NewNATSManger(kubeClient, helmRenderer, sugaredLogger) + collector := metrics.NewPrometheusCollector() + collector.RegisterMetrics() + // create NATS reconciler instance natsReconciler := nmctrl.NewReconciler( mgr.GetClient(), @@ -165,6 +169,7 @@ func main() { //nolint:funlen // main function needs to initialize many objects Namespace: envConfigs.NATSCRNamespace, }, }, + collector, ) if err = (natsReconciler).SetupWithManager(mgr); err != nil { diff --git a/internal/controller/nats/controller.go b/internal/controller/nats/controller.go index 5f1a585c..4425d164 100644 --- a/internal/controller/nats/controller.go +++ b/internal/controller/nats/controller.go @@ -19,6 +19,7 @@ package nats import ( "context" "fmt" + "github.com/kyma-project/nats-manager/internal/metrics" nmapiv1alpha1 "github.com/kyma-project/nats-manager/api/v1alpha1" "github.com/kyma-project/nats-manager/pkg/events" @@ -70,6 +71,7 @@ type Reconciler struct { ctrlManager kcontrollerruntime.Manager destinationRuleWatchStarted bool allowedNATSCR *nmapiv1alpha1.NATS + collector metrics.Collector } func NewReconciler( @@ -81,6 +83,7 @@ func NewReconciler( recorder record.EventRecorder, natsManager nmmgr.Manager, allowedNATSCR *nmapiv1alpha1.NATS, + collector metrics.Collector, ) *Reconciler { return &Reconciler{ Client: client, @@ -93,6 +96,7 @@ func NewReconciler( natsManager: natsManager, destinationRuleWatchStarted: false, allowedNATSCR: allowedNATSCR, + collector: collector, controller: nil, } } diff --git a/internal/controller/nats/deprovisioner.go b/internal/controller/nats/deprovisioner.go index ccbbbb74..69161c01 100644 --- a/internal/controller/nats/deprovisioner.go +++ b/internal/controller/nats/deprovisioner.go @@ -24,6 +24,10 @@ const ( func (r *Reconciler) handleNATSDeletion(ctx context.Context, nats *nmapiv1alpha1.NATS, log *zap.SugaredLogger, ) (kcontrollerruntime.Result, error) { + // reset metrics. + r.collector.ResetAvailabilityZonesUsedMetric() + r.collector.ResetClusterSizeMetric() + // skip reconciliation for deletion if the finalizer is not set. if !r.containsFinalizer(nats) { log.Debugf("skipped reconciliation for deletion as finalizer is not set.") diff --git a/internal/controller/nats/provisioner.go b/internal/controller/nats/provisioner.go index c26b0868..36ef1c4b 100644 --- a/internal/controller/nats/provisioner.go +++ b/internal/controller/nats/provisioner.go @@ -23,6 +23,9 @@ func (r *Reconciler) handleNATSReconcile(ctx context.Context, ) (kcontrollerruntime.Result, error) { log.Info("handling NATS reconciliation...") + // record metric. + r.collector.RecordClusterSizeMetric(nats.Spec.Cluster.Size) + // set status to processing nats.Status.Initialize() events.Normal(r.recorder, nats, nmapiv1alpha1.ConditionReasonProcessing, "Initializing NATS resource.") @@ -101,6 +104,9 @@ func (r *Reconciler) handleNATSState(ctx context.Context, nats *nmapiv1alpha1.NA nats.Status.AvailabilityZonesUsed, err = r.kubeClient.GetNumberOfAvailabilityZonesUsedByPods(ctx, nats.GetNamespace(), getNATSPodsMatchLabels()) + // record metric. + r.collector.RecordAvailabilityZonesUsedMetric(nats.Status.AvailabilityZonesUsed) + switch { case err != nil: nats.Status.UpdateConditionAvailabilityZones(kmetav1.ConditionFalse, diff --git a/internal/controller/nats/unit_test.go b/internal/controller/nats/unit_test.go index a53b21e2..fed7c14b 100644 --- a/internal/controller/nats/unit_test.go +++ b/internal/controller/nats/unit_test.go @@ -2,6 +2,7 @@ package nats import ( "context" + "github.com/kyma-project/nats-manager/internal/metrics" "testing" nmapiv1alpha1 "github.com/kyma-project/nats-manager/api/v1alpha1" @@ -60,6 +61,9 @@ func NewMockedUnitTestEnvironment(t *testing.T, objs ...client.Object) *MockedUn mockController := new(nmctrlmocks.Controller) mockManager := new(nmctrlmocks.Manager) + // setup mocks. + collector := metrics.NewPrometheusCollector() + // setup reconciler reconciler := NewReconciler( fakeClient, @@ -70,6 +74,7 @@ func NewMockedUnitTestEnvironment(t *testing.T, objs ...client.Object) *MockedUn recorder, natsManager, nil, + collector, ) reconciler.controller = mockController reconciler.ctrlManager = mockManager diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 00000000..15d49ba0 --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,90 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +const ( + // availabilityZonesUsedMetricKey name of the availability zones used metric. + availabilityZonesUsedMetricKey = "nats_manager_availability_zones_used" + // availabilityZonesUsedHelp help text for the availability zones used metric. + availabilityZonesUsedHelp = "The number of availability zones used used by NATS Pods." + + // clusterSizeMetricKey name of the cluster size metric. + clusterSizeMetricKey = "nats_manager_cr_cluster_size" + // clusterSizeMetricHelp help text for the cluster size metric. + clusterSizeMetricHelp = "The cluster size configured in the NATS CR." +) + +// Perform a compile time check. +var _ Collector = &PrometheusCollector{} + +//go:generate go run github.com/vektra/mockery/v2 --name=Collector --outpkg=mocks --case=underscore +type Collector interface { + RegisterMetrics() + RecordAvailabilityZonesUsedMetric(int) + RecordClusterSizeMetric(int) + ResetAvailabilityZonesUsedMetric() + ResetClusterSizeMetric() +} + +// PrometheusCollector implements the prometheus.Collector interface. +type PrometheusCollector struct { + availabilityZonesUsed *prometheus.GaugeVec + clusterSize *prometheus.GaugeVec +} + +// NewPrometheusCollector a new instance of Collector. +func NewPrometheusCollector() Collector { + return &PrometheusCollector{ + availabilityZonesUsed: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: availabilityZonesUsedMetricKey, + Help: availabilityZonesUsedHelp, + }, + nil, + ), + clusterSize: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: clusterSizeMetricKey, + Help: clusterSizeMetricHelp, + }, + nil, + ), + } +} + +// Describe implements the prometheus.Collector interface Describe method. +func (p *PrometheusCollector) Describe(ch chan<- *prometheus.Desc) { + p.availabilityZonesUsed.Describe(ch) + p.clusterSize.Describe(ch) +} + +// Collect implements the prometheus.Collector interface Collect method. +func (p *PrometheusCollector) Collect(ch chan<- prometheus.Metric) { + p.availabilityZonesUsed.Collect(ch) + p.clusterSize.Collect(ch) +} + +// RegisterMetrics registers the metrics. +func (p *PrometheusCollector) RegisterMetrics() { + metrics.Registry.MustRegister(p.availabilityZonesUsed) + metrics.Registry.MustRegister(p.clusterSize) +} + +func (p *PrometheusCollector) RecordAvailabilityZonesUsedMetric(availabilityZonesUsed int) { + p.availabilityZonesUsed.WithLabelValues().Set(float64(availabilityZonesUsed)) +} + +func (p *PrometheusCollector) RecordClusterSizeMetric(clusterSize int) { + p.clusterSize.WithLabelValues().Set(float64(clusterSize)) +} + +func (p *PrometheusCollector) ResetAvailabilityZonesUsedMetric() { + p.availabilityZonesUsed.Reset() +} + +func (p *PrometheusCollector) ResetClusterSizeMetric() { + p.clusterSize.Reset() +} diff --git a/internal/metrics/mocks/collector.go b/internal/metrics/mocks/collector.go new file mode 100644 index 00000000..733261b2 --- /dev/null +++ b/internal/metrics/mocks/collector.go @@ -0,0 +1,194 @@ +// Code generated by mockery v2.42.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Collector is an autogenerated mock type for the Collector type +type Collector struct { + mock.Mock +} + +type Collector_Expecter struct { + mock *mock.Mock +} + +func (_m *Collector) EXPECT() *Collector_Expecter { + return &Collector_Expecter{mock: &_m.Mock} +} + +// RecordAvailabilityZonesUsedMetric provides a mock function with given fields: availabilityZonesUsed +func (_m *Collector) RecordAvailabilityZonesUsedMetric(availabilityZonesUsed int) { + _m.Called(availabilityZonesUsed) +} + +// Collector_RecordAvailabilityZonesUsedMetric_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecordAvailabilityZonesUsedMetric' +type Collector_RecordAvailabilityZonesUsedMetric_Call struct { + *mock.Call +} + +// RecordAvailabilityZonesUsedMetric is a helper method to define mock.On call +// - availabilityZonesUsed int +func (_e *Collector_Expecter) RecordAvailabilityZonesUsedMetric(availabilityZonesUsed interface{}) *Collector_RecordAvailabilityZonesUsedMetric_Call { + return &Collector_RecordAvailabilityZonesUsedMetric_Call{Call: _e.mock.On("RecordAvailabilityZonesUsedMetric", availabilityZonesUsed)} +} + +func (_c *Collector_RecordAvailabilityZonesUsedMetric_Call) Run(run func(availabilityZonesUsed int)) *Collector_RecordAvailabilityZonesUsedMetric_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(int)) + }) + return _c +} + +func (_c *Collector_RecordAvailabilityZonesUsedMetric_Call) Return() *Collector_RecordAvailabilityZonesUsedMetric_Call { + _c.Call.Return() + return _c +} + +func (_c *Collector_RecordAvailabilityZonesUsedMetric_Call) RunAndReturn(run func(int)) *Collector_RecordAvailabilityZonesUsedMetric_Call { + _c.Call.Return(run) + return _c +} + +// RecordClusterSizeMetric provides a mock function with given fields: clusterSize +func (_m *Collector) RecordClusterSizeMetric(clusterSize int) { + _m.Called(clusterSize) +} + +// Collector_RecordClusterSizeMetric_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecordClusterSizeMetric' +type Collector_RecordClusterSizeMetric_Call struct { + *mock.Call +} + +// RecordClusterSizeMetric is a helper method to define mock.On call +// - clusterSize int +func (_e *Collector_Expecter) RecordClusterSizeMetric(clusterSize interface{}) *Collector_RecordClusterSizeMetric_Call { + return &Collector_RecordClusterSizeMetric_Call{Call: _e.mock.On("RecordClusterSizeMetric", clusterSize)} +} + +func (_c *Collector_RecordClusterSizeMetric_Call) Run(run func(clusterSize int)) *Collector_RecordClusterSizeMetric_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(int)) + }) + return _c +} + +func (_c *Collector_RecordClusterSizeMetric_Call) Return() *Collector_RecordClusterSizeMetric_Call { + _c.Call.Return() + return _c +} + +func (_c *Collector_RecordClusterSizeMetric_Call) RunAndReturn(run func(int)) *Collector_RecordClusterSizeMetric_Call { + _c.Call.Return(run) + return _c +} + +// RegisterMetrics provides a mock function with given fields: +func (_m *Collector) RegisterMetrics() { + _m.Called() +} + +// Collector_RegisterMetrics_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterMetrics' +type Collector_RegisterMetrics_Call struct { + *mock.Call +} + +// RegisterMetrics is a helper method to define mock.On call +func (_e *Collector_Expecter) RegisterMetrics() *Collector_RegisterMetrics_Call { + return &Collector_RegisterMetrics_Call{Call: _e.mock.On("RegisterMetrics")} +} + +func (_c *Collector_RegisterMetrics_Call) Run(run func()) *Collector_RegisterMetrics_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Collector_RegisterMetrics_Call) Return() *Collector_RegisterMetrics_Call { + _c.Call.Return() + return _c +} + +func (_c *Collector_RegisterMetrics_Call) RunAndReturn(run func()) *Collector_RegisterMetrics_Call { + _c.Call.Return(run) + return _c +} + +// ResetAvailabilityZonesUsedMetric provides a mock function with given fields: +func (_m *Collector) ResetAvailabilityZonesUsedMetric() { + _m.Called() +} + +// Collector_ResetAvailabilityZonesUsedMetric_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetAvailabilityZonesUsedMetric' +type Collector_ResetAvailabilityZonesUsedMetric_Call struct { + *mock.Call +} + +// ResetAvailabilityZonesUsedMetric is a helper method to define mock.On call +func (_e *Collector_Expecter) ResetAvailabilityZonesUsedMetric() *Collector_ResetAvailabilityZonesUsedMetric_Call { + return &Collector_ResetAvailabilityZonesUsedMetric_Call{Call: _e.mock.On("ResetAvailabilityZonesUsedMetric")} +} + +func (_c *Collector_ResetAvailabilityZonesUsedMetric_Call) Run(run func()) *Collector_ResetAvailabilityZonesUsedMetric_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Collector_ResetAvailabilityZonesUsedMetric_Call) Return() *Collector_ResetAvailabilityZonesUsedMetric_Call { + _c.Call.Return() + return _c +} + +func (_c *Collector_ResetAvailabilityZonesUsedMetric_Call) RunAndReturn(run func()) *Collector_ResetAvailabilityZonesUsedMetric_Call { + _c.Call.Return(run) + return _c +} + +// ResetClusterSizeMetric provides a mock function with given fields: +func (_m *Collector) ResetClusterSizeMetric() { + _m.Called() +} + +// Collector_ResetClusterSizeMetric_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetClusterSizeMetric' +type Collector_ResetClusterSizeMetric_Call struct { + *mock.Call +} + +// ResetClusterSizeMetric is a helper method to define mock.On call +func (_e *Collector_Expecter) ResetClusterSizeMetric() *Collector_ResetClusterSizeMetric_Call { + return &Collector_ResetClusterSizeMetric_Call{Call: _e.mock.On("ResetClusterSizeMetric")} +} + +func (_c *Collector_ResetClusterSizeMetric_Call) Run(run func()) *Collector_ResetClusterSizeMetric_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Collector_ResetClusterSizeMetric_Call) Return() *Collector_ResetClusterSizeMetric_Call { + _c.Call.Return() + return _c +} + +func (_c *Collector_ResetClusterSizeMetric_Call) RunAndReturn(run func()) *Collector_ResetClusterSizeMetric_Call { + _c.Call.Return(run) + return _c +} + +// NewCollector creates a new instance of Collector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCollector(t interface { + mock.TestingT + Cleanup(func()) +}) *Collector { + mock := &Collector{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutils/integration/integration.go b/testutils/integration/integration.go index 3e6adfb9..ef738bb5 100644 --- a/testutils/integration/integration.go +++ b/testutils/integration/integration.go @@ -3,6 +3,7 @@ package integration import ( "context" "fmt" + "github.com/kyma-project/nats-manager/internal/metrics" "log" "path/filepath" "reflect" @@ -143,6 +144,10 @@ func NewTestEnvironment(projectRootDir string, celValidationEnabled bool, // create NATS manager instance natsManager := nmmgr.NewNATSManger(kubeClient, helmRenderer, sugaredLogger) + // create metrics collector. + collector := metrics.NewPrometheusCollector() + collector.RegisterMetrics() + // setup reconciler natsReconciler := nmctrl.NewReconciler( ctrlMgr.GetClient(), @@ -153,6 +158,7 @@ func NewTestEnvironment(projectRootDir string, celValidationEnabled bool, recorder, natsManager, allowedNATSCR, + collector, ) if err = (natsReconciler).SetupWithManager(ctrlMgr); err != nil { return nil, err