From dd5c269520db3ad326d15958f87ba846fd3ca213 Mon Sep 17 00:00:00 2001 From: hjoshi123 Date: Tue, 6 May 2025 10:48:13 -0600 Subject: [PATCH 1/7] adding unit test and controller for cert challenge Signed-off-by: hjoshi123 --- .../acmechallenges/metrics/controller.go | 111 ++++++++++++++++++ pkg/metrics/acme.go | 23 ++++ pkg/metrics/acme_test.go | 51 ++++++++ pkg/metrics/metrics.go | 9 ++ 4 files changed, 194 insertions(+) create mode 100644 pkg/controller/acmechallenges/metrics/controller.go create mode 100644 pkg/metrics/acme_test.go diff --git a/pkg/controller/acmechallenges/metrics/controller.go b/pkg/controller/acmechallenges/metrics/controller.go new file mode 100644 index 00000000000..027f32efa6b --- /dev/null +++ b/pkg/controller/acmechallenges/metrics/controller.go @@ -0,0 +1,111 @@ +/* + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package metrics + +import ( + "context" + "fmt" + + cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" + "github.com/cert-manager/cert-manager/pkg/metrics" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" +) + +const ( + // ControllerName is the string used to refer to this controller + // when enabling or disabling it from command line flags. + ControllerName = "certificates-metrics" +) + +// controllerWrapper wraps the `controller` structure to make it implement +// the controllerpkg.queueingController interface +type controllerWrapper struct { + *controller +} + +// This controller is synced on all Certificate 'create', 'update', and +// 'delete' events which will update the metrics for that Certificate. +type controller struct { + certificateChallengeListers cmacmelisters.ChallengeLister + + metrics *metrics.Metrics +} + +func NewController(ctx *controllerpkg.Context) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { + // create a queue used to queue up items to be processed + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) + + certificateChallengeInformer := ctx.SharedInformerFactory.Acme().V1().Challenges() + + // handle all events when challenge is created, updated, or deleted. Delete shouldn't matter for challenges + // but leaving the behavior of the default queueing event handler. + if _, err := certificateChallengeInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{ + Queue: queue, + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + + // build a list of InformerSynced functions that will be returned by the + // Register method. the controller will only begin processing items once all + // of these informers have synced. + mustSync := []cache.InformerSynced{ + certificateChallengeInformer.Informer().HasSynced, + } + + return &controller{ + certificateChallengeListers: certificateChallengeInformer.Lister(), + metrics: ctx.Metrics, + }, queue, mustSync, nil +} + +func (c *controller) ProcessItem(ctx context.Context, namespace types.NamespacedName) error { + ns, name := namespace.Namespace, namespace.Name + + challenge, err := c.certificateChallengeListers.Challenges(ns).Get(name) + if apierrors.IsNotFound(err) { + c.metrics.RemoveChallengeStatus(challenge) + return nil + } + if err != nil { + return err + } + + c.metrics.UpdateChallengeStatus(challenge) + return nil +} + +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { + ctrl, queue, mustSync, err := NewController(ctx) + c.controller = ctrl + return queue, mustSync, err +} + +func init() { + controllerpkg.Register(ControllerName, func(ctx *controllerpkg.ContextFactory) (controllerpkg.Interface, error) { + return controllerpkg.NewBuilder(ctx, ControllerName). + For(&controllerWrapper{}). + Complete() + }) +} diff --git a/pkg/metrics/acme.go b/pkg/metrics/acme.go index 0bb130d3dd6..17351f6ad8e 100644 --- a/pkg/metrics/acme.go +++ b/pkg/metrics/acme.go @@ -17,7 +17,11 @@ limitations under the License. package metrics import ( + "fmt" "time" + + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/prometheus/client_golang/prometheus" ) // ObserveACMERequestDuration increases bucket counters for that ACME client duration. @@ -29,3 +33,22 @@ func (m *Metrics) ObserveACMERequestDuration(duration time.Duration, labels ...s func (m *Metrics) IncrementACMERequestCount(labels ...string) { m.acmeClientRequestCount.WithLabelValues(labels...).Inc() } + +func (m *Metrics) UpdateChallengeStatus(challenge *acmev1.Challenge) { + value := 0.0 + if challenge.Status.State == acmev1.Ready { + value = 1.0 + } + m.certificateChallenegeStatus.With(prometheus.Labels{ + "status": string(challenge.Status.State), + "reason": string(challenge.Status.Reason), + "domain": challenge.Spec.DNSName, + "processing": fmt.Sprint(challenge.Status.Processing), + }).Set(value) +} + +func (m *Metrics) RemoveChallengeStatus(challenge *acmev1.Challenge) { + m.certificateChallenegeStatus.DeletePartialMatch(prometheus.Labels{ + "domain": challenge.Spec.DNSName, + }) +} diff --git a/pkg/metrics/acme_test.go b/pkg/metrics/acme_test.go new file mode 100644 index 00000000000..d0573a8cd86 --- /dev/null +++ b/pkg/metrics/acme_test.go @@ -0,0 +1,51 @@ +package metrics + +import ( + "strings" + "testing" + + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/test/unit/gen" + "github.com/go-logr/logr/testr" + "github.com/prometheus/client_golang/prometheus/testutil" + "k8s.io/utils/clock" +) + +const certificateChallengeStatusMetadata = ` + # HELP certmanager_certificate_challenge_status The status of certificate challenges. + # TYPE certmanager_certificate_challenge_status gauge +` + +func TestCertificateChallengeStatusMetrics(t *testing.T) { + type TestChallenge struct { + challenge *cmacme.Challenge + expectedMetric string + } + + testCases := map[string]TestChallenge{ + "challenge-metric-active-state-valid": { + challenge: gen.Challenge("test-challenge-status", + gen.SetChallengeDNSName("example.com"), + gen.SetChallengeProcessing(false), + gen.SetChallengeState(cmacme.Ready), + ), + expectedMetric: ` + certmanager_certificate_challenge_status{domain="example.com",processing="false",reason="",status="ready"} 1 + `, + }, + } + + for testName, test := range testCases { + t.Run(testName, func(t *testing.T) { + m := New(testr.New(t), clock.RealClock{}) + m.UpdateChallengeStatus(test.challenge) + + if err := testutil.CollectAndCompare(m.certificateChallenegeStatus, + strings.NewReader(certificateChallengeStatusMetadata+test.expectedMetric), + "certmanager_certificate_challenge_status", + ); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + }) + } +} diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index abea96e7e9b..9e721a616f9 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -65,6 +65,7 @@ type Metrics struct { venafiClientRequestDurationSeconds *prometheus.SummaryVec controllerSyncCallCount *prometheus.CounterVec controllerSyncErrorCount *prometheus.CounterVec + certificateChallenegeStatus *prometheus.GaugeVec } var readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} @@ -205,6 +206,12 @@ func New(log logr.Logger, c clock.Clock) *Metrics { }, []string{"controller"}, ) + + certificateChallenegeStatus = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "certificate_challenge_status", + Help: "The status of certificate challenges.", + }, []string{"status", "domain", "reason", "processing"}) ) // Create Registry and register the recommended collectors @@ -230,6 +237,7 @@ func New(log logr.Logger, c clock.Clock) *Metrics { venafiClientRequestDurationSeconds: venafiClientRequestDurationSeconds, controllerSyncCallCount: controllerSyncCallCount, controllerSyncErrorCount: controllerSyncErrorCount, + certificateChallenegeStatus: certificateChallenegeStatus, } return m @@ -249,6 +257,7 @@ func (m *Metrics) NewServer(ln net.Listener) *http.Server { m.registry.MustRegister(m.acmeClientRequestCount) m.registry.MustRegister(m.controllerSyncCallCount) m.registry.MustRegister(m.controllerSyncErrorCount) + m.registry.MustRegister(m.certificateChallenegeStatus) mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{})) From 501a401e3b55b9f7831d6a5e94b332a6934904e8 Mon Sep 17 00:00:00 2001 From: hjoshi123 Date: Tue, 20 May 2025 08:33:54 -0600 Subject: [PATCH 2/7] added uid for deletion and removed value Signed-off-by: hjoshi123 --- pkg/controller/acmechallenges/metrics/controller.go | 3 ++- pkg/metrics/acme.go | 13 ++++++------- pkg/metrics/acme_test.go | 6 ++++-- pkg/metrics/metrics.go | 11 ++++++----- test/unit/gen/challenge.go | 7 +++++++ 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/pkg/controller/acmechallenges/metrics/controller.go b/pkg/controller/acmechallenges/metrics/controller.go index 027f32efa6b..0101e313c74 100644 --- a/pkg/controller/acmechallenges/metrics/controller.go +++ b/pkg/controller/acmechallenges/metrics/controller.go @@ -1,4 +1,5 @@ /* +Copyright 2020 The cert-manager Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,7 +32,7 @@ import ( const ( // ControllerName is the string used to refer to this controller // when enabling or disabling it from command line flags. - ControllerName = "certificates-metrics" + ControllerName = "certificate-challenges-metrics" ) // controllerWrapper wraps the `controller` structure to make it implement diff --git a/pkg/metrics/acme.go b/pkg/metrics/acme.go index 17351f6ad8e..a4b9e2b71b8 100644 --- a/pkg/metrics/acme.go +++ b/pkg/metrics/acme.go @@ -35,20 +35,19 @@ func (m *Metrics) IncrementACMERequestCount(labels ...string) { } func (m *Metrics) UpdateChallengeStatus(challenge *acmev1.Challenge) { - value := 0.0 - if challenge.Status.State == acmev1.Ready { - value = 1.0 - } - m.certificateChallenegeStatus.With(prometheus.Labels{ + value := 1.0 + m.certificateChallengeStatus.With(prometheus.Labels{ "status": string(challenge.Status.State), "reason": string(challenge.Status.Reason), "domain": challenge.Spec.DNSName, + "type": string(challenge.Spec.Type), + "id": string(challenge.GetUID()), "processing": fmt.Sprint(challenge.Status.Processing), }).Set(value) } func (m *Metrics) RemoveChallengeStatus(challenge *acmev1.Challenge) { - m.certificateChallenegeStatus.DeletePartialMatch(prometheus.Labels{ - "domain": challenge.Spec.DNSName, + m.certificateChallengeStatus.DeletePartialMatch(prometheus.Labels{ + "id": string(challenge.GetUID()), }) } diff --git a/pkg/metrics/acme_test.go b/pkg/metrics/acme_test.go index d0573a8cd86..f8777dbad4a 100644 --- a/pkg/metrics/acme_test.go +++ b/pkg/metrics/acme_test.go @@ -27,10 +27,12 @@ func TestCertificateChallengeStatusMetrics(t *testing.T) { challenge: gen.Challenge("test-challenge-status", gen.SetChallengeDNSName("example.com"), gen.SetChallengeProcessing(false), + gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01), gen.SetChallengeState(cmacme.Ready), + gen.SetChallengeUID("test-challenge-uid"), ), expectedMetric: ` - certmanager_certificate_challenge_status{domain="example.com",processing="false",reason="",status="ready"} 1 + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="ready",type="DNS-01"} 1 `, }, } @@ -40,7 +42,7 @@ func TestCertificateChallengeStatusMetrics(t *testing.T) { m := New(testr.New(t), clock.RealClock{}) m.UpdateChallengeStatus(test.challenge) - if err := testutil.CollectAndCompare(m.certificateChallenegeStatus, + if err := testutil.CollectAndCompare(m.certificateChallengeStatus, strings.NewReader(certificateChallengeStatusMetadata+test.expectedMetric), "certmanager_certificate_challenge_status", ); err != nil { diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 9e721a616f9..7670f80a257 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -19,6 +19,7 @@ limitations under the License. // certificate_expiration_timestamp_seconds{name, namespace, issuer_name, issuer_kind, issuer_group} // certificate_renewal_timestamp_seconds{name, namespace, issuer_name, issuer_kind, issuer_group} // certificate_ready_status{name, namespace, condition, issuer_name, issuer_kind, issuer_group} +// certificate_challenge_status{status, domain, reason, processing, id, type} // acme_client_request_count{"scheme", "host", "path", "method", "status"} // acme_client_request_duration_seconds{"scheme", "host", "path", "method", "status"} // venafi_client_request_duration_seconds{"scheme", "host", "path", "method", "status"} @@ -65,7 +66,7 @@ type Metrics struct { venafiClientRequestDurationSeconds *prometheus.SummaryVec controllerSyncCallCount *prometheus.CounterVec controllerSyncErrorCount *prometheus.CounterVec - certificateChallenegeStatus *prometheus.GaugeVec + certificateChallengeStatus *prometheus.GaugeVec } var readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} @@ -207,11 +208,11 @@ func New(log logr.Logger, c clock.Clock) *Metrics { []string{"controller"}, ) - certificateChallenegeStatus = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + certificateChallengeStatus = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: namespace, Name: "certificate_challenge_status", Help: "The status of certificate challenges.", - }, []string{"status", "domain", "reason", "processing"}) + }, []string{"status", "domain", "reason", "processing", "id", "type"}) ) // Create Registry and register the recommended collectors @@ -237,7 +238,7 @@ func New(log logr.Logger, c clock.Clock) *Metrics { venafiClientRequestDurationSeconds: venafiClientRequestDurationSeconds, controllerSyncCallCount: controllerSyncCallCount, controllerSyncErrorCount: controllerSyncErrorCount, - certificateChallenegeStatus: certificateChallenegeStatus, + certificateChallengeStatus: certificateChallengeStatus, } return m @@ -257,7 +258,7 @@ func (m *Metrics) NewServer(ln net.Listener) *http.Server { m.registry.MustRegister(m.acmeClientRequestCount) m.registry.MustRegister(m.controllerSyncCallCount) m.registry.MustRegister(m.controllerSyncErrorCount) - m.registry.MustRegister(m.certificateChallenegeStatus) + m.registry.MustRegister(m.certificateChallengeStatus) mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{})) diff --git a/test/unit/gen/challenge.go b/test/unit/gen/challenge.go index 712e509edc1..9307c192ae2 100644 --- a/test/unit/gen/challenge.go +++ b/test/unit/gen/challenge.go @@ -18,6 +18,7 @@ package gen import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" @@ -128,6 +129,12 @@ func SetChallengeDeletionTimestamp(ts metav1.Time) ChallengeModifier { } } +func SetChallengeUID(uid string) ChallengeModifier { + return func(ch *cmacme.Challenge) { + ch.UID = types.UID(uid) + } +} + func ResetChallengeStatus() ChallengeModifier { return func(ch *cmacme.Challenge) { ch.Status = cmacme.ChallengeStatus{} From 2e50b3d536fb21df78d7f07242f080aef4e48c82 Mon Sep 17 00:00:00 2001 From: hjoshi123 Date: Tue, 20 May 2025 08:52:44 -0600 Subject: [PATCH 3/7] added license to acme_test Signed-off-by: hjoshi123 --- pkg/metrics/acme_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/metrics/acme_test.go b/pkg/metrics/acme_test.go index f8777dbad4a..a0b5abf10be 100644 --- a/pkg/metrics/acme_test.go +++ b/pkg/metrics/acme_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package metrics import ( From d9b1594e0c6e2cd0a8984ccf47c7631aa0e32eb9 Mon Sep 17 00:00:00 2001 From: hjoshi123 Date: Tue, 20 May 2025 14:07:29 -0600 Subject: [PATCH 4/7] fixing license issues Signed-off-by: hjoshi123 --- pkg/controller/acmechallenges/metrics/controller.go | 9 +++++---- pkg/metrics/acme.go | 5 +++-- pkg/metrics/acme_test.go | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pkg/controller/acmechallenges/metrics/controller.go b/pkg/controller/acmechallenges/metrics/controller.go index 0101e313c74..f132c428964 100644 --- a/pkg/controller/acmechallenges/metrics/controller.go +++ b/pkg/controller/acmechallenges/metrics/controller.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2025 The cert-manager Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ import ( "context" "fmt" - cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" - "github.com/cert-manager/cert-manager/pkg/metrics" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" + + cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" + "github.com/cert-manager/cert-manager/pkg/metrics" ) const ( diff --git a/pkg/metrics/acme.go b/pkg/metrics/acme.go index a4b9e2b71b8..0beb225f638 100644 --- a/pkg/metrics/acme.go +++ b/pkg/metrics/acme.go @@ -20,8 +20,9 @@ import ( "fmt" "time" - acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/prometheus/client_golang/prometheus" + + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" ) // ObserveACMERequestDuration increases bucket counters for that ACME client duration. @@ -38,7 +39,7 @@ func (m *Metrics) UpdateChallengeStatus(challenge *acmev1.Challenge) { value := 1.0 m.certificateChallengeStatus.With(prometheus.Labels{ "status": string(challenge.Status.State), - "reason": string(challenge.Status.Reason), + "reason": challenge.Status.Reason, "domain": challenge.Spec.DNSName, "type": string(challenge.Spec.Type), "id": string(challenge.GetUID()), diff --git a/pkg/metrics/acme_test.go b/pkg/metrics/acme_test.go index a0b5abf10be..d157816984b 100644 --- a/pkg/metrics/acme_test.go +++ b/pkg/metrics/acme_test.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2025 The cert-manager Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,11 +20,12 @@ import ( "strings" "testing" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - "github.com/cert-manager/cert-manager/test/unit/gen" "github.com/go-logr/logr/testr" "github.com/prometheus/client_golang/prometheus/testutil" "k8s.io/utils/clock" + + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/test/unit/gen" ) const certificateChallengeStatusMetadata = ` From d9a02848d43d3fdb7334530c8e2c4f867615ea76 Mon Sep 17 00:00:00 2001 From: hjoshi123 Date: Wed, 28 May 2025 10:41:51 -0600 Subject: [PATCH 5/7] added better tests and value change on valid status Signed-off-by: hjoshi123 --- .../config/controller/v1alpha1/defaults.go | 2 ++ pkg/metrics/acme.go | 9 +++++- pkg/metrics/acme_test.go | 30 +++++++++++++------ pkg/metrics/metrics.go | 6 +++- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/internal/apis/config/controller/v1alpha1/defaults.go b/internal/apis/config/controller/v1alpha1/defaults.go index 614b9819473..1b3b98f1c21 100644 --- a/internal/apis/config/controller/v1alpha1/defaults.go +++ b/internal/apis/config/controller/v1alpha1/defaults.go @@ -27,6 +27,7 @@ import ( "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" sharedv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" challengescontroller "github.com/cert-manager/cert-manager/pkg/controller/acmechallenges" + acmechallengesmetricscontroller "github.com/cert-manager/cert-manager/pkg/controller/acmechallenges/metrics" orderscontroller "github.com/cert-manager/cert-manager/pkg/controller/acmeorders" shimgatewaycontroller "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim/gateways" shimingresscontroller "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim/ingresses" @@ -150,6 +151,7 @@ var ( requestmanager.ControllerName, readiness.ControllerName, revisionmanager.ControllerName, + acmechallengesmetricscontroller.ControllerName, } ExperimentalCertificateSigningRequestControllers = []string{ diff --git a/pkg/metrics/acme.go b/pkg/metrics/acme.go index 0beb225f638..867d6448907 100644 --- a/pkg/metrics/acme.go +++ b/pkg/metrics/acme.go @@ -36,7 +36,14 @@ func (m *Metrics) IncrementACMERequestCount(labels ...string) { } func (m *Metrics) UpdateChallengeStatus(challenge *acmev1.Challenge) { - value := 1.0 + value := 0.0 + + for _, status := range challengeValidStatuses { + if string(challenge.Status.State) == string(status) { + value = 1.0 + break + } + } m.certificateChallengeStatus.With(prometheus.Labels{ "status": string(challenge.Status.State), "reason": challenge.Status.Reason, diff --git a/pkg/metrics/acme_test.go b/pkg/metrics/acme_test.go index d157816984b..e8da8872801 100644 --- a/pkg/metrics/acme_test.go +++ b/pkg/metrics/acme_test.go @@ -35,20 +35,29 @@ const certificateChallengeStatusMetadata = ` func TestCertificateChallengeStatusMetrics(t *testing.T) { type TestChallenge struct { - challenge *cmacme.Challenge + challenges []*cmacme.Challenge expectedMetric string } + pendingToValidChallenges := make([]*cmacme.Challenge, 0) + pendingToValidChallenges = append(pendingToValidChallenges, gen.Challenge("test-challenge-status", + gen.SetChallengeDNSName("example.com"), + gen.SetChallengeProcessing(false), + gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01), + gen.SetChallengeState(cmacme.Pending), + gen.SetChallengeUID("test-challenge-uid"), + ), gen.Challenge("test-challenge-status-2", + gen.SetChallengeDNSName("example.com"), + gen.SetChallengeProcessing(false), + gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01), + gen.SetChallengeState(cmacme.Ready), + gen.SetChallengeUID("test-challenge-uid"), + )) testCases := map[string]TestChallenge{ "challenge-metric-active-state-valid": { - challenge: gen.Challenge("test-challenge-status", - gen.SetChallengeDNSName("example.com"), - gen.SetChallengeProcessing(false), - gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01), - gen.SetChallengeState(cmacme.Ready), - gen.SetChallengeUID("test-challenge-uid"), - ), + challenges: pendingToValidChallenges, expectedMetric: ` + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="pending",type="DNS-01"} 0 certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="ready",type="DNS-01"} 1 `, }, @@ -57,7 +66,10 @@ func TestCertificateChallengeStatusMetrics(t *testing.T) { for testName, test := range testCases { t.Run(testName, func(t *testing.T) { m := New(testr.New(t), clock.RealClock{}) - m.UpdateChallengeStatus(test.challenge) + + for _, challenge := range test.challenges { + m.UpdateChallengeStatus(challenge) + } if err := testutil.CollectAndCompare(m.certificateChallengeStatus, strings.NewReader(certificateChallengeStatusMetadata+test.expectedMetric), diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 7670f80a257..c47fb1a5342 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -37,6 +37,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "k8s.io/utils/clock" + acmemeta "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" ) @@ -69,7 +70,10 @@ type Metrics struct { certificateChallengeStatus *prometheus.GaugeVec } -var readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} +var ( + readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} + challengeValidStatuses = [...]acmemeta.State{acmemeta.Ready, acmemeta.Valid} +) // New creates a Metrics struct and populates it with prometheus metric types. func New(log logr.Logger, c clock.Clock) *Metrics { From 23dd8d7b58bd63505015eb099d52ef1775f64a2b Mon Sep 17 00:00:00 2001 From: hjoshi123 Date: Wed, 28 May 2025 13:53:10 -0600 Subject: [PATCH 6/7] added all possible states Signed-off-by: hjoshi123 --- pkg/metrics/acme.go | 21 ++++++++++----------- pkg/metrics/acme_test.go | 6 ++++++ pkg/metrics/metrics.go | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/pkg/metrics/acme.go b/pkg/metrics/acme.go index 867d6448907..2cfd6c9ee32 100644 --- a/pkg/metrics/acme.go +++ b/pkg/metrics/acme.go @@ -36,22 +36,21 @@ func (m *Metrics) IncrementACMERequestCount(labels ...string) { } func (m *Metrics) UpdateChallengeStatus(challenge *acmev1.Challenge) { - value := 0.0 - for _, status := range challengeValidStatuses { + value := 0.0 if string(challenge.Status.State) == string(status) { value = 1.0 - break } + + m.certificateChallengeStatus.With(prometheus.Labels{ + "status": string(status), + "reason": challenge.Status.Reason, + "domain": challenge.Spec.DNSName, + "type": string(challenge.Spec.Type), + "id": string(challenge.GetUID()), + "processing": fmt.Sprint(challenge.Status.Processing), + }).Set(value) } - m.certificateChallengeStatus.With(prometheus.Labels{ - "status": string(challenge.Status.State), - "reason": challenge.Status.Reason, - "domain": challenge.Spec.DNSName, - "type": string(challenge.Spec.Type), - "id": string(challenge.GetUID()), - "processing": fmt.Sprint(challenge.Status.Processing), - }).Set(value) } func (m *Metrics) RemoveChallengeStatus(challenge *acmev1.Challenge) { diff --git a/pkg/metrics/acme_test.go b/pkg/metrics/acme_test.go index e8da8872801..2dbbe6202b8 100644 --- a/pkg/metrics/acme_test.go +++ b/pkg/metrics/acme_test.go @@ -57,8 +57,14 @@ func TestCertificateChallengeStatusMetrics(t *testing.T) { "challenge-metric-active-state-valid": { challenges: pendingToValidChallenges, expectedMetric: ` + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="",type="DNS-01"} 0 + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="errored",type="DNS-01"} 0 + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="expired",type="DNS-01"} 0 + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="invalid",type="DNS-01"} 0 + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="processing",type="DNS-01"} 0 certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="pending",type="DNS-01"} 0 certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="ready",type="DNS-01"} 1 + certmanager_certificate_challenge_status{domain="example.com",id="test-challenge-uid",processing="false",reason="",status="valid",type="DNS-01"} 0 `, }, } diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index c47fb1a5342..b62eb30d026 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -72,7 +72,7 @@ type Metrics struct { var ( readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} - challengeValidStatuses = [...]acmemeta.State{acmemeta.Ready, acmemeta.Valid} + challengeValidStatuses = [...]acmemeta.State{acmemeta.Ready, acmemeta.Valid, acmemeta.Errored, acmemeta.Expired, acmemeta.Invalid, acmemeta.Processing, acmemeta.Unknown, acmemeta.Pending} ) // New creates a Metrics struct and populates it with prometheus metric types. From 0b471f8861cda84d7c0de8f1203647bf5de86912 Mon Sep 17 00:00:00 2001 From: hjoshi123 Date: Wed, 28 May 2025 15:19:39 -0600 Subject: [PATCH 7/7] added acme metrics controller to allcontrollers Signed-off-by: hjoshi123 --- internal/apis/config/controller/v1alpha1/defaults.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/apis/config/controller/v1alpha1/defaults.go b/internal/apis/config/controller/v1alpha1/defaults.go index 1b3b98f1c21..45cad5947f5 100644 --- a/internal/apis/config/controller/v1alpha1/defaults.go +++ b/internal/apis/config/controller/v1alpha1/defaults.go @@ -129,6 +129,7 @@ var ( csrselfsignedcontroller.CSRControllerName, csrvenaficontroller.CSRControllerName, csrvaultcontroller.CSRControllerName, + acmechallengesmetricscontroller.ControllerName, } DefaultEnabledControllers = []string{