diff --git a/CHANGELOG.md b/CHANGELOG.md index c7286bd..9eccebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Log when something goes wrong with GarbageCollection. - Use a queue based system to sync items. +- Added `/_healthz` endpoint and set up probes. +- Added `/metrics` endpoint for Prometheus. ### Fixed @@ -20,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Set up lower resource requests/limits for the Deployment. - MonitorTemplate is now namespace scoped instead of cluster scoped. +- The operator now uses cache informers to reconcile state. ## v0.1.1 - 2018-09-02 diff --git a/Gopkg.lock b/Gopkg.lock index c3b9d7e..cacdd1a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -9,6 +9,14 @@ pruneopts = "" revision = "85edefcbdaa39278c9db647d135d1dc8bccdad96" +[[projects]] + branch = "master" + digest = "1:c0bec5f9b98d0bc872ff5e834fac186b807b656683bd29cb82fb207a1513fabb" + name = "github.com/beorn7/perks" + packages = ["quantile"] + pruneopts = "" + revision = "3a771d992973f24aa725d07868b467d1ddfceafb" + [[projects]] digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" name = "github.com/davecgh/go-spew" @@ -164,6 +172,14 @@ revision = "c2353362d570a7bfa228149c62842019201cfb71" version = "v1.8.0" +[[projects]] + digest = "1:63722a4b1e1717be7b98fc686e0b30d5e7f734b9e93d7dee86293b6deab7ea28" + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + pruneopts = "" + revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" + version = "v1.0.1" + [[projects]] digest = "1:5219b4506253ccc598f9340677162a42d6a78f340a4cc6df2d62db4d0593c4e9" name = "github.com/mitchellh/mapstructure" @@ -196,6 +212,47 @@ revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" version = "v1.2.0" +[[projects]] + digest = "1:4142d94383572e74b42352273652c62afec5b23f325222ed09198f46009022d1" + name = "github.com/prometheus/client_golang" + packages = ["prometheus"] + pruneopts = "" + revision = "c5b7fccd204277076155f10851dad72b76a49317" + version = "v0.8.0" + +[[projects]] + branch = "master" + digest = "1:185cf55b1f44a1bf243558901c3f06efa5c64ba62cfdcbb1bf7bbe8c3fb68561" + name = "github.com/prometheus/client_model" + packages = ["go"] + pruneopts = "" + revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" + +[[projects]] + branch = "master" + digest = "1:f477ef7b65d94fb17574fc6548cef0c99a69c1634ea3b6da248b63a61ebe0498" + name = "github.com/prometheus/common" + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model", + ] + pruneopts = "" + revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" + +[[projects]] + branch = "master" + digest = "1:e04aaa0e8f8da0ed3d6c0700bd77eda52a47f38510063209d72d62f0ef807d5e" + name = "github.com/prometheus/procfs" + packages = [ + ".", + "internal/util", + "nfs", + "xfs", + ] + pruneopts = "" + revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" + [[projects]] digest = "1:7ba2551c9a8de293bc575dbe2c0d862c52252d26f267f784547f059f512471c8" name = "github.com/spf13/afero" @@ -431,6 +488,46 @@ packages = [ "discovery", "discovery/fake", + "informers", + "informers/admissionregistration", + "informers/admissionregistration/v1alpha1", + "informers/admissionregistration/v1beta1", + "informers/apps", + "informers/apps/v1", + "informers/apps/v1beta1", + "informers/apps/v1beta2", + "informers/autoscaling", + "informers/autoscaling/v1", + "informers/autoscaling/v2beta1", + "informers/batch", + "informers/batch/v1", + "informers/batch/v1beta1", + "informers/batch/v2alpha1", + "informers/certificates", + "informers/certificates/v1beta1", + "informers/core", + "informers/core/v1", + "informers/events", + "informers/events/v1beta1", + "informers/extensions", + "informers/extensions/v1beta1", + "informers/internalinterfaces", + "informers/networking", + "informers/networking/v1", + "informers/policy", + "informers/policy/v1beta1", + "informers/rbac", + "informers/rbac/v1", + "informers/rbac/v1alpha1", + "informers/rbac/v1beta1", + "informers/scheduling", + "informers/scheduling/v1alpha1", + "informers/settings", + "informers/settings/v1alpha1", + "informers/storage", + "informers/storage/v1", + "informers/storage/v1alpha1", + "informers/storage/v1beta1", "kubernetes", "kubernetes/fake", "kubernetes/scheme", @@ -490,6 +587,30 @@ "kubernetes/typed/storage/v1alpha1/fake", "kubernetes/typed/storage/v1beta1", "kubernetes/typed/storage/v1beta1/fake", + "listers/admissionregistration/v1alpha1", + "listers/admissionregistration/v1beta1", + "listers/apps/v1", + "listers/apps/v1beta1", + "listers/apps/v1beta2", + "listers/autoscaling/v1", + "listers/autoscaling/v2beta1", + "listers/batch/v1", + "listers/batch/v1beta1", + "listers/batch/v2alpha1", + "listers/certificates/v1beta1", + "listers/core/v1", + "listers/events/v1beta1", + "listers/extensions/v1beta1", + "listers/networking/v1", + "listers/policy/v1beta1", + "listers/rbac/v1", + "listers/rbac/v1alpha1", + "listers/rbac/v1beta1", + "listers/scheduling/v1alpha1", + "listers/settings/v1alpha1", + "listers/storage/v1", + "listers/storage/v1alpha1", + "listers/storage/v1beta1", "pkg/apis/clientauthentication", "pkg/apis/clientauthentication/v1alpha1", "pkg/version", @@ -513,6 +634,7 @@ "util/homedir", "util/integer", "util/retry", + "util/workqueue", ] pruneopts = "" revision = "23781f4d6632d88e869066eaebb743857aa1ef9b" @@ -583,6 +705,7 @@ "github.com/DreamItGetIT/statuscake", "github.com/dchest/blake2b", "github.com/golang/glog", + "github.com/prometheus/client_golang/prometheus", "github.com/spf13/cobra", "github.com/spf13/viper", "k8s.io/api/core/v1", @@ -595,17 +718,21 @@ "k8s.io/apimachinery/pkg/runtime/schema", "k8s.io/apimachinery/pkg/runtime/serializer", "k8s.io/apimachinery/pkg/types", + "k8s.io/apimachinery/pkg/util/wait", "k8s.io/apimachinery/pkg/watch", "k8s.io/client-go/discovery", "k8s.io/client-go/discovery/fake", + "k8s.io/client-go/informers", "k8s.io/client-go/kubernetes", "k8s.io/client-go/kubernetes/fake", "k8s.io/client-go/kubernetes/scheme", + "k8s.io/client-go/listers/extensions/v1beta1", "k8s.io/client-go/rest", "k8s.io/client-go/testing", "k8s.io/client-go/tools/cache", "k8s.io/client-go/tools/clientcmd", "k8s.io/client-go/util/flowcontrol", + "k8s.io/client-go/util/workqueue", "k8s.io/code-generator/cmd/client-gen", "k8s.io/code-generator/cmd/deepcopy-gen", "k8s.io/code-generator/cmd/defaulter-gen", diff --git a/Gopkg.toml b/Gopkg.toml index 1fa0bf9..84360a1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -36,3 +36,7 @@ required = [ [[constraint]] branch = "master" name = "github.com/DreamItGetIT/statuscake" + +[[constraint]] + name = "github.com/prometheus/client_golang" + version = "0.8.0" diff --git a/internal/httpsvc/health.go b/internal/httpsvc/health.go deleted file mode 100644 index 46864a7..0000000 --- a/internal/httpsvc/health.go +++ /dev/null @@ -1,25 +0,0 @@ -package httpsvc - -import ( - "fmt" - "net/http" -) - -// Health represents a Health server which can be used to expose a health check. -type Health struct { - Server -} - -// Start starts the Health Server. -func (s *Health) Start(stopCh <-chan struct{}) error { - registerHealthCheck(&s.ServeMux) - - return s.Server.Start(stopCh) -} - -func registerHealthCheck(mux *http.ServeMux) { - mux.HandleFunc("/_healthz", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, "OK") - }) -} diff --git a/internal/httpsvc/metrics.go b/internal/httpsvc/metrics.go new file mode 100644 index 0000000..09924a8 --- /dev/null +++ b/internal/httpsvc/metrics.go @@ -0,0 +1,35 @@ +package httpsvc + +import ( + "fmt" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// Metrics represents a Metrics server which can be used to expose a prometheus +// metrics. +type Metrics struct { + Server + *prometheus.Registry +} + +// Start starts the Health Server. +func (s *Metrics) Start(stopCh <-chan struct{}) error { + registerMetrics(&s.ServeMux, s.Registry) + registerHealthCheck(&s.ServeMux) + + return s.Server.Start(stopCh) +} + +func registerMetrics(mux *http.ServeMux, reg *prometheus.Registry) { + mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) +} + +func registerHealthCheck(mux *http.ServeMux) { + mux.HandleFunc("/_healthz", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, "OK") + }) +} diff --git a/internal/ingressmonitor/cmd/operator.go b/internal/ingressmonitor/cmd/operator.go index 5393e8f..3d5b6da 100644 --- a/internal/ingressmonitor/cmd/operator.go +++ b/internal/ingressmonitor/cmd/operator.go @@ -2,16 +2,20 @@ package cmd import ( "log" + "os" "time" "github.com/jelmersnoeck/ingress-monitor/internal/httpsvc" "github.com/jelmersnoeck/ingress-monitor/internal/ingressmonitor" + "github.com/jelmersnoeck/ingress-monitor/internal/metrics" "github.com/jelmersnoeck/ingress-monitor/internal/provider" "github.com/jelmersnoeck/ingress-monitor/internal/provider/logger" "github.com/jelmersnoeck/ingress-monitor/internal/provider/statuscake" "github.com/jelmersnoeck/ingress-monitor/internal/signals" "github.com/jelmersnoeck/ingress-monitor/pkg/client/generated/clientset/versioned" + "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" @@ -24,8 +28,8 @@ var operatorFlags struct { KubeConfig string ResyncPeriod string - HTTPAddr string - HTTPPort int + MetricsAddr string + MetricsPort int } // operatorCmd represents the operator command @@ -63,18 +67,25 @@ func runOperator(cmd *cobra.Command, args []string) { statuscake.Register(fact) logger.Register(fact) - // register the health server - healthSrv := &httpsvc.Health{ + // create new prometheus registry + registry := prometheus.NewRegistry() + registry.MustRegister(prometheus.NewProcessCollector(os.Getpid(), "")) + registry.MustRegister(prometheus.NewGoCollector()) + + // new metrics collector + mtrc := metrics.New(registry) + metricssvc := httpsvc.Metrics{ Server: httpsvc.Server{ - Addr: operatorFlags.HTTPAddr, - Port: operatorFlags.HTTPPort, + Addr: operatorFlags.MetricsAddr, + Port: operatorFlags.MetricsPort, }, + Registry: registry, } - go healthSrv.Start(stopCh) + go metricssvc.Start(stopCh) op, err := ingressmonitor.NewOperator( kubeClient, imClient, operatorFlags.Namespace, - resync, fact, + resync, fact, mtrc, ) if err != nil { log.Fatalf("Error building IngressMonitor Operator: %s", err) @@ -93,6 +104,6 @@ func init() { operatorCmd.PersistentFlags().StringVar(&operatorFlags.KubeConfig, "kubeconfig", "", "Kubeconfig which should be used to talk to the API.") operatorCmd.PersistentFlags().StringVar(&operatorFlags.ResyncPeriod, "resync-period", "30s", "Resyncing period to ensure all monitors are up to date.") - operatorCmd.PersistentFlags().StringVar(&operatorFlags.HTTPAddr, "http-addr", "0.0.0.0", "address the health server will bind to") - operatorCmd.PersistentFlags().IntVar(&operatorFlags.HTTPPort, "http-port", 9090, "port on which the health server is available") + operatorCmd.PersistentFlags().StringVar(&operatorFlags.MetricsAddr, "metrics-addr", "0.0.0.0", "address the metrics server will bind to") + operatorCmd.PersistentFlags().IntVar(&operatorFlags.MetricsPort, "metrics-port", 9090, "port on which the metrics server is available") } diff --git a/internal/ingressmonitor/operator.go b/internal/ingressmonitor/operator.go index abf5a37..e8d37da 100644 --- a/internal/ingressmonitor/operator.go +++ b/internal/ingressmonitor/operator.go @@ -11,6 +11,7 @@ import ( "time" "github.com/jelmersnoeck/ingress-monitor/apis/ingressmonitor/v1alpha1" + "github.com/jelmersnoeck/ingress-monitor/internal/metrics" "github.com/jelmersnoeck/ingress-monitor/internal/provider" "github.com/jelmersnoeck/ingress-monitor/pkg/client/generated/clientset/versioned" crdscheme "github.com/jelmersnoeck/ingress-monitor/pkg/client/generated/clientset/versioned/scheme" @@ -49,6 +50,7 @@ var ( type Operator struct { kubeClient kubernetes.Interface imClient tv1alpha1.IngressmonitorV1alpha1Interface + metrics *metrics.Metrics providerFactory provider.FactoryInterface @@ -78,7 +80,8 @@ type namedInformer struct { func NewOperator( kc kubernetes.Interface, imc versioned.Interface, namespace string, resync time.Duration, - providerFactory provider.FactoryInterface) (*Operator, error) { + providerFactory provider.FactoryInterface, + mtrcs *metrics.Metrics) (*Operator, error) { // Register the scheme with the client so we can use it through the API crdscheme.AddToScheme(scheme.Scheme) @@ -92,6 +95,7 @@ func NewOperator( providerFactory: providerFactory, monitorQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Monitors"), ingressMonitorQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "IngressMonitors"), + metrics: mtrcs, imInformer: imInformer.IngressMonitors().Informer(), mInformer: imInformer.Monitors().Informer(), @@ -280,6 +284,8 @@ func (o *Operator) enqueueMonitor(m *v1alpha1.Monitor) { func (o *Operator) OnAdd(obj interface{}) { switch obj := obj.(type) { case *v1alpha1.IngressMonitor: + o.metrics.AddIngressMonitor(ingressMonitorMetric(obj, nil)) + o.enqueueIngressMonitor(obj) case *v1alpha1.Monitor: o.enqueueMonitor(obj) @@ -302,6 +308,8 @@ func (o *Operator) OnUpdate(old, new interface{}) { func (o *Operator) OnDelete(obj interface{}) { switch obj := obj.(type) { case *v1alpha1.IngressMonitor: + o.metrics.DeleteIngressMonitor(ingressMonitorMetric(obj, nil)) + cl, err := o.providerFactory.From(obj.Spec.Provider) if err != nil { log.Printf("Could not get provider for IngressMonitor %s:%s: %s", obj.Namespace, obj.Name, err) @@ -332,7 +340,7 @@ func (o *Operator) OnDelete(obj interface{}) { // handleIngressMonitor handles IngressMonitors in a way that it knows how to // deal with creating and updating resources. -func (o *Operator) handleIngressMonitor(key string) error { +func (o *Operator) handleIngressMonitor(key string) (err error) { item, exists, err := o.imInformer.GetIndexer().GetByKey(key) if err != nil { return err @@ -345,6 +353,11 @@ func (o *Operator) handleIngressMonitor(key string) error { obj := item.(*v1alpha1.IngressMonitor) + // XXX handle indexer errors + defer func() { + o.metrics.SyncIngressMonitor(ingressMonitorMetric(obj, err)) + }() + cl, err := o.providerFactory.From(obj.Spec.Provider) if err != nil { return fmt.Errorf("Error fetching provider '%s': %s", obj.Spec.Provider.Type, err) @@ -607,3 +620,15 @@ func templatedName(ing *v1beta1.Ingress, sp v1alpha1.MonitorTemplateSpec) (strin } return buf.String(), nil } + +func ingressMonitorMetric(obj *v1alpha1.IngressMonitor, err error) metrics.IngressMonitorMetric { + var success bool + if err == nil { + success = true + } + return metrics.IngressMonitorMetric{ + Namespace: obj.Namespace, + Name: obj.Name, + Success: success, + } +} diff --git a/internal/ingressmonitor/operator_test.go b/internal/ingressmonitor/operator_test.go index 988001a..2e51a6e 100644 --- a/internal/ingressmonitor/operator_test.go +++ b/internal/ingressmonitor/operator_test.go @@ -7,9 +7,11 @@ import ( "time" "github.com/jelmersnoeck/ingress-monitor/apis/ingressmonitor/v1alpha1" + "github.com/jelmersnoeck/ingress-monitor/internal/metrics" "github.com/jelmersnoeck/ingress-monitor/internal/provider" "github.com/jelmersnoeck/ingress-monitor/internal/provider/fake" imfake "github.com/jelmersnoeck/ingress-monitor/pkg/client/generated/clientset/versioned/fake" + "github.com/prometheus/client_golang/prometheus" "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" @@ -395,10 +397,16 @@ func newOperator(t *testing.T, opts ...optionFunc) *operatorWrapper { opt(cfg) } + registry := prometheus.NewRegistry() + mtrc := metrics.New(registry) + k8sClient := k8sfake.NewSimpleClientset(cfg.kubeObjects...) crdClient := imfake.NewSimpleClientset(cfg.crdObjects...) fact := provider.NewFactory(nil) - op, err := NewOperator(k8sClient, crdClient, v1.NamespaceAll, noResyncPeriodFunc(), fact) + op, err := NewOperator( + k8sClient, crdClient, v1.NamespaceAll, + noResyncPeriodFunc(), fact, mtrc, + ) if err != nil { t.Fatalf("Error creating the operator: %s", err) } diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 0000000..4cc76fb --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,107 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +const ( + ingressMonitorTotalGauge = "ingressmonitor_ingressmonitor_total" + ingressMonitorSyncGauge = "ingressmonitor_ingressmonitor_sync_total" + ingressMonitorFailedGauge = "ingressmonitor_ingressmonitor_failed_total" + ingressMonitorSuccessGauge = "ingressmonitor_ingressmonitor_success_total" +) + +// Namespaced represent a type which has a namespace attached to it. +type Namespaced interface { + Namespace() string +} + +// Metrics is a wrapper for the metrics we use within the operator. +type Metrics struct { + ingressMonitorTotalGauge *prometheus.GaugeVec + ingressMonitorSyncGauge *prometheus.GaugeVec + ingressMonitorFailedGauge *prometheus.GaugeVec + ingressMonitorSuccessGauge *prometheus.GaugeVec +} + +// IngressMonitorMetric represents a metric which will be used to capture +// information about an IngressMonitor. +type IngressMonitorMetric struct { + Namespace string + Name string + Success bool +} + +// AddIngressMonitor adds an extra IngressMonitor to the IngressMonitor Gauge +// for the namespace it's created in. +func (m *Metrics) AddIngressMonitor(obj IngressMonitorMetric) { + m.ingressMonitorTotalGauge.WithLabelValues(obj.Namespace).Inc() +} + +// DeleteIngressMonitor deletes an IngressMonitor to the IngressMonitor Gauge from +// the namespace it's deleted from. +func (m *Metrics) DeleteIngressMonitor(obj IngressMonitorMetric) { + m.ingressMonitorTotalGauge.WithLabelValues(obj.Namespace).Dec() +} + +// SyncIngressMonitor sets up the metrics for a sync action for an +// IngressMonitorMetric. +func (m *Metrics) SyncIngressMonitor(obj IngressMonitorMetric) { + if obj.Success { + m.ingressMonitorSuccessGauge.WithLabelValues(obj.Namespace).Inc() + } else { + m.ingressMonitorFailedGauge.WithLabelValues(obj.Namespace).Inc() + } + + m.ingressMonitorSyncGauge.WithLabelValues(obj.Namespace).Inc() +} + +// New returns a new metrics handler which registers all it's metrics with the +// specified prometheus Registry to broadcast it's captured values. +func New(reg *prometheus.Registry) *Metrics { + m := &Metrics{ + ingressMonitorTotalGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: ingressMonitorTotalGauge, + Help: "Total number of Ingress Monitors in the cluster", + }, + []string{"namespace"}, + ), + + ingressMonitorSyncGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: ingressMonitorSyncGauge, + Help: "Total number of sync operations performed on the Ingress Monitors", + }, + []string{"namespace"}, + ), + + ingressMonitorFailedGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: ingressMonitorFailedGauge, + Help: "Total number of failed syncs for the Ingress Monitors", + }, + []string{"namespace"}, + ), + + ingressMonitorSuccessGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: ingressMonitorSuccessGauge, + Help: "Total number of successful syncs for the Ingress Monitors", + }, + []string{"namespace"}, + ), + } + + m.register(reg) + return m +} + +func (m *Metrics) register(reg *prometheus.Registry) { + reg.MustRegister( + m.ingressMonitorTotalGauge, + m.ingressMonitorSyncGauge, + m.ingressMonitorFailedGauge, + m.ingressMonitorSuccessGauge, + ) +} diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go new file mode 100644 index 0000000..8288ce5 --- /dev/null +++ b/internal/metrics/metrics_test.go @@ -0,0 +1,174 @@ +package metrics + +import ( + "reflect" + "testing" + + "github.com/prometheus/client_golang/prometheus" + mprom "github.com/prometheus/client_model/go" +) + +func TestMetrics_IngressMonitor(t *testing.T) { + t.Run("adding an ingress monitor", func(t *testing.T) { + reg := prometheus.NewRegistry() + m := New(reg) + m.AddIngressMonitor(IngressMonitorMetric{Namespace: "testing"}) + + gatherers := prometheus.Gatherers{ + reg, + prometheus.DefaultGatherer, + } + + gathering, err := gatherers.Gather() + if err != nil { + t.Fatal(err) + } + + var testMetric []*mprom.Metric + for _, gath := range gathering { + if gath.GetName() == ingressMonitorTotalGauge { + testMetric = gath.Metric + } + } + + exp := []*mprom.Metric{ + { + Label: []*mprom.LabelPair{labelPair("namespace", "testing")}, + Gauge: &mprom.Gauge{Value: ptrFloat64(1)}, + }, + } + + if !reflect.DeepEqual(testMetric, exp) { + t.Errorf("Gathered metric\n\n%#v\n\n doesn't equal expected metric\n\n%#v\n\n", testMetric, exp) + } + }) + + t.Run("deleting an ingress monitor", func(t *testing.T) { + reg := prometheus.NewRegistry() + m := New(reg) + m.DeleteIngressMonitor(IngressMonitorMetric{Namespace: "testing"}) + + gatherers := prometheus.Gatherers{ + reg, + prometheus.DefaultGatherer, + } + + gathering, err := gatherers.Gather() + if err != nil { + t.Fatal(err) + } + + var testMetric []*mprom.Metric + for _, gath := range gathering { + if gath.GetName() == ingressMonitorTotalGauge { + testMetric = gath.Metric + } + } + + exp := []*mprom.Metric{ + { + Label: []*mprom.LabelPair{labelPair("namespace", "testing")}, + Gauge: &mprom.Gauge{Value: ptrFloat64(-1)}, + }, + } + + if !reflect.DeepEqual(testMetric, exp) { + t.Errorf("Gathered metric\n\n%#v\n\n doesn't equal expected metric\n\n%#v\n\n", testMetric, exp) + } + }) + + t.Run("syncing an ingress monitor", func(t *testing.T) { + tests := []struct { + name string + imm IngressMonitorMetric + gm string + metric []*mprom.Metric + }{ + { + name: "successfully synced", + imm: IngressMonitorMetric{Namespace: "testing", Success: true}, + gm: ingressMonitorSuccessGauge, + metric: []*mprom.Metric{ + { + Label: []*mprom.LabelPair{labelPair("namespace", "testing")}, + Gauge: &mprom.Gauge{Value: ptrFloat64(1)}, + }, + }, + }, + { + name: "unsuccessfully synced", + imm: IngressMonitorMetric{Namespace: "testing", Success: false}, + gm: ingressMonitorFailedGauge, + metric: []*mprom.Metric{ + { + Label: []*mprom.LabelPair{labelPair("namespace", "testing")}, + Gauge: &mprom.Gauge{Value: ptrFloat64(1)}, + }, + }, + }, + { + name: "successfully synced", + imm: IngressMonitorMetric{Namespace: "testing", Success: true}, + gm: ingressMonitorSyncGauge, + metric: []*mprom.Metric{ + { + Label: []*mprom.LabelPair{labelPair("namespace", "testing")}, + Gauge: &mprom.Gauge{Value: ptrFloat64(1)}, + }, + }, + }, + { + name: "unsuccessfully synced", + imm: IngressMonitorMetric{Namespace: "testing", Success: false}, + gm: ingressMonitorSyncGauge, + metric: []*mprom.Metric{ + { + Label: []*mprom.LabelPair{labelPair("namespace", "testing")}, + Gauge: &mprom.Gauge{Value: ptrFloat64(1)}, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + reg := prometheus.NewRegistry() + m := New(reg) + m.SyncIngressMonitor(test.imm) + + gatherers := prometheus.Gatherers{ + reg, + prometheus.DefaultGatherer, + } + + gathering, err := gatherers.Gather() + if err != nil { + t.Fatal(err) + } + + var testMetric []*mprom.Metric + for _, gath := range gathering { + if gath.GetName() == test.gm { + testMetric = test.metric + } + } + + if !reflect.DeepEqual(testMetric, test.metric) { + t.Errorf("Gathered metric\n\n%#v\n\n doesn't equal expected metric\n\n%#v\n\n", testMetric, test.metric) + } + }) + } + }) +} + +func labelPair(name, value string) *mprom.LabelPair { + return &mprom.LabelPair{Name: ptrString(name), Value: ptrString(value)} +} + +func ptrString(s string) *string { + return &s +} + +func ptrFloat64(f float64) *float64 { + return &f +}