From e8fec124145f2856dd4cef81ba20707bb22926d5 Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Sun, 20 Sep 2026 16:23:30 +0200 Subject: [PATCH] feat: Classifier agentFailureMessage Sveltos-agent now reports any error it encouters processing a Classifier instance. The error is reported in Status.AgentFailureMessage. Advance sveltos-agent and sveltos-applier images to pick those changes. --- controllers/classifier_report_collection.go | 4 + go.mod | 2 +- go.sum | 4 +- pkg/agent/sveltos-agent-in-mgmt-cluster.go | 2 +- pkg/agent/sveltos-agent-in-mgmt-cluster.yaml | 2 +- pkg/agent/sveltos-agent.go | 2 +- pkg/agent/sveltos-agent.yaml | 2 +- pkg/agent/sveltos-applier.go | 2 +- pkg/agent/sveltos-applier.yaml | 2 +- test/fv/agent_failure_test.go | 107 +++++++++++++++++++ test/fv/fv_suite_test.go | 6 ++ test/pullmode-sveltosapplier.yaml | 2 +- 12 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 test/fv/agent_failure_test.go diff --git a/controllers/classifier_report_collection.go b/controllers/classifier_report_collection.go index c3b3ade..02f04a7 100644 --- a/controllers/classifier_report_collection.go +++ b/controllers/classifier_report_collection.go @@ -485,6 +485,10 @@ func processOneClassifierReport(ctx context.Context, c, clusterClient client.Cli return } + // AgentFailureMessage is set by sveltos-agent itself, in the managed cluster, when it cannot + // refresh cr.Spec.Match; carry it over so the management-cluster copy reflects it too. + mgmtClassifierReport.Status.AgentFailureMessage = cr.Status.AgentFailureMessage + if err := updateClassifierReportStatus(ctx, c, mgmtClassifierReport, logger); err != nil { return } diff --git a/go.mod b/go.mod index eef4579..d0099e6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/onsi/ginkgo/v2 v2.32.2 github.com/onsi/gomega v1.43.0 github.com/pkg/errors v0.9.1 - github.com/projectsveltos/libsveltos v1.15.1-0.20260916064556-4b7dcd8c38fd + github.com/projectsveltos/libsveltos v1.15.1-0.20260920085014-ab4903957d08 github.com/prometheus/client_golang v1.24.1 github.com/spf13/pflag v1.0.10 github.com/yuin/gopher-lua v1.1.2 diff --git a/go.sum b/go.sum index fe2502f..d7676ad 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/projectsveltos/libsveltos v1.15.1-0.20260916064556-4b7dcd8c38fd h1:DDcvUiTbr37R6tHMrBtMFjcRtr39VGV1Mm/dVdhyQwE= -github.com/projectsveltos/libsveltos v1.15.1-0.20260916064556-4b7dcd8c38fd/go.mod h1:YKMqwREDkoQH84WTmixxsB8tN5V0cwOnWQ5hNjrg7xM= +github.com/projectsveltos/libsveltos v1.15.1-0.20260920085014-ab4903957d08 h1:QKTx/oxpWj77Cj07A2KwlhLtkQ7P4lb4sIK74sg7Ots= +github.com/projectsveltos/libsveltos v1.15.1-0.20260920085014-ab4903957d08/go.mod h1:YKMqwREDkoQH84WTmixxsB8tN5V0cwOnWQ5hNjrg7xM= github.com/projectsveltos/lua-utils/glua-json v0.0.0-20251212200258-2b3cdcb7c0f5 h1:khnc+994UszxZYu69J+R5FKiLA/Nk1JQj0EYAkwTWz0= github.com/projectsveltos/lua-utils/glua-json v0.0.0-20251212200258-2b3cdcb7c0f5/go.mod h1:yVL8KQFa9tmcxgwl9nwIMtKgtmIVC1zaFRSCfOwYvPY= github.com/projectsveltos/lua-utils/glua-runes v0.0.0-20251212200258-2b3cdcb7c0f5 h1:YbsebwRwTRhV8QacvEAdFqxcxHdeu7JTVtsBovbkgos= diff --git a/pkg/agent/sveltos-agent-in-mgmt-cluster.go b/pkg/agent/sveltos-agent-in-mgmt-cluster.go index 73926df..18346e0 100644 --- a/pkg/agent/sveltos-agent-in-mgmt-cluster.go +++ b/pkg/agent/sveltos-agent-in-mgmt-cluster.go @@ -63,7 +63,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-agent@sha256:c3636950f22bcb668b28129554309ab602a166c6fab51441ef385cf0aa8034f2 + image: docker.io/projectsveltos/sveltos-agent@sha256:b96485435933ac28581c310bf0a79093e5e80c2fb614095ee68f332f25d77cb0 livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/agent/sveltos-agent-in-mgmt-cluster.yaml b/pkg/agent/sveltos-agent-in-mgmt-cluster.yaml index 9228953..af38c56 100644 --- a/pkg/agent/sveltos-agent-in-mgmt-cluster.yaml +++ b/pkg/agent/sveltos-agent-in-mgmt-cluster.yaml @@ -45,7 +45,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-agent@sha256:c3636950f22bcb668b28129554309ab602a166c6fab51441ef385cf0aa8034f2 + image: docker.io/projectsveltos/sveltos-agent@sha256:b96485435933ac28581c310bf0a79093e5e80c2fb614095ee68f332f25d77cb0 livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/agent/sveltos-agent.go b/pkg/agent/sveltos-agent.go index 4cf6837..f3046cb 100644 --- a/pkg/agent/sveltos-agent.go +++ b/pkg/agent/sveltos-agent.go @@ -212,7 +212,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-agent@sha256:c3636950f22bcb668b28129554309ab602a166c6fab51441ef385cf0aa8034f2 + image: docker.io/projectsveltos/sveltos-agent@sha256:b96485435933ac28581c310bf0a79093e5e80c2fb614095ee68f332f25d77cb0 livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/agent/sveltos-agent.yaml b/pkg/agent/sveltos-agent.yaml index 722be8b..937efdd 100644 --- a/pkg/agent/sveltos-agent.yaml +++ b/pkg/agent/sveltos-agent.yaml @@ -194,7 +194,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-agent@sha256:c3636950f22bcb668b28129554309ab602a166c6fab51441ef385cf0aa8034f2 + image: docker.io/projectsveltos/sveltos-agent@sha256:b96485435933ac28581c310bf0a79093e5e80c2fb614095ee68f332f25d77cb0 livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/agent/sveltos-applier.go b/pkg/agent/sveltos-applier.go index a5ef1a7..26ebe6a 100644 --- a/pkg/agent/sveltos-applier.go +++ b/pkg/agent/sveltos-applier.go @@ -118,7 +118,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-applier@sha256:6bea4f06b8d1d3fa988b018045727c715fce63d3d46b143e6b392aefc062e828 + image: docker.io/projectsveltos/sveltos-applier@sha256:942a429b7d7ffbb5b598a26230454b29c9a8b3cf904fef1941594c0e26b04b09 livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/agent/sveltos-applier.yaml b/pkg/agent/sveltos-applier.yaml index e9e143d..5f34fdc 100644 --- a/pkg/agent/sveltos-applier.yaml +++ b/pkg/agent/sveltos-applier.yaml @@ -100,7 +100,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-applier@sha256:6bea4f06b8d1d3fa988b018045727c715fce63d3d46b143e6b392aefc062e828 + image: docker.io/projectsveltos/sveltos-applier@sha256:942a429b7d7ffbb5b598a26230454b29c9a8b3cf904fef1941594c0e26b04b09 livenessProbe: failureThreshold: 3 httpGet: diff --git a/test/fv/agent_failure_test.go b/test/fv/agent_failure_test.go new file mode 100644 index 0000000..59a811b --- /dev/null +++ b/test/fv/agent_failure_test.go @@ -0,0 +1,107 @@ +/* +Copyright 2026. Sveltos SRL. All rights reserved. + +This file is part of Sveltos Enterprise. See the LICENSE file at the root +of this repository. +*/ + +package fv_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" +) + +var _ = Describe("Classifier: sveltos-agent evaluation failure is surfaced", func() { + const namePrefix = "agent-failure-" + + It("AgentFailureMessage is set when sveltos-agent's evaluation fails, and cleared once fixed, "+ + "without disturbing the last known-good Spec.Match", Label("FV", "PULLMODE"), func() { + + classifier := &libsveltosv1beta1.Classifier{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix + randomString(), + }, + Spec: libsveltosv1beta1.ClassifierSpec{ + ClassifierLabels: []libsveltosv1beta1.ClassifierLabel{ + {Key: key, Value: value}, + }, + KubernetesVersionConstraints: []libsveltosv1beta1.KubernetesVersionConstraint{ + { + Version: "1.20.0", + Comparison: string(libsveltosv1beta1.ComparisonGreaterThanOrEqualTo), + }, + }, + DeployedResourceConstraint: &libsveltosv1beta1.DeployedResourceConstraint{ + // Namespace is cluster-scoped and always has at least one instance + // (kube-system, default, ...), so this is a guaranteed match as long + // as Evaluate does not error out. + ResourceSelectors: []libsveltosv1beta1.ResourceSelector{ + { + Group: "", + Version: "v1", + Kind: "Namespace", + }, + }, + }, + }, + } + + Byf("Creating classifier instance %s in the management cluster", classifier.Name) + Expect(k8sClient.Create(context.TODO(), classifier)).To(Succeed()) + + Byf("Verifying the classifier is a match before any agent-side error is introduced") + verifyClassfierIsProvisioned(classifier) + verifyClassifierReport(classifier.Name, true) + verifyAgentFailureMessage(classifier.Name, false) + + Byf("Introducing a broken Lua evaluate script, so sveltos-agent's evaluation errors out") + currentClassifier := &libsveltosv1beta1.Classifier{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: classifier.Name}, + currentClassifier)).To(Succeed()) + currentClassifier.Spec.DeployedResourceConstraint.ResourceSelectors[0].Evaluate = "this is not valid lua {{{" + Expect(k8sClient.Update(context.TODO(), currentClassifier)).To(Succeed()) + + Byf("Verifying AgentFailureMessage gets set, while Spec.Match stays at its last known-good value") + verifyAgentFailureMessage(classifier.Name, true) + verifyClassifierReport(classifier.Name, true) + + Byf("Fixing the Lua evaluate script") + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: classifier.Name}, + currentClassifier)).To(Succeed()) + currentClassifier.Spec.DeployedResourceConstraint.ResourceSelectors[0].Evaluate = "" + Expect(k8sClient.Update(context.TODO(), currentClassifier)).To(Succeed()) + + Byf("Verifying AgentFailureMessage is cleared once evaluation succeeds again") + verifyAgentFailureMessage(classifier.Name, false) + verifyClassifierReport(classifier.Name, true) + }) +}) + +// verifyAgentFailureMessage waits for ClassifierReport.Status.AgentFailureMessage to be set (or +// cleared, if wantSet is false), on the management cluster's copy of the report. +func verifyAgentFailureMessage(classifierName string, wantSet bool) { + clusterType := libsveltosv1beta1.ClusterTypeCapi + if kindWorkloadCluster.GetKind() == libsveltosv1beta1.SveltosClusterKind { + clusterType = libsveltosv1beta1.ClusterTypeSveltos + } + classifierReportName := libsveltosv1beta1.GetClassifierReportName(classifierName, kindWorkloadCluster.GetName(), &clusterType) + Byf("Verifying ClassifierReport %s AgentFailureMessage is set: %t", classifierReportName, wantSet) + Eventually(func() bool { + currentClassifierReport := &libsveltosv1beta1.ClassifierReport{} + err := k8sClient.Get(context.TODO(), + types.NamespacedName{Namespace: kindWorkloadCluster.GetNamespace(), Name: classifierReportName}, + currentClassifierReport) + if err != nil { + return false + } + return (currentClassifierReport.Status.AgentFailureMessage != nil) == wantSet + }, timeout, pollingInterval).Should(BeTrue()) +} diff --git a/test/fv/fv_suite_test.go b/test/fv/fv_suite_test.go index a64731d..23879af 100644 --- a/test/fv/fv_suite_test.go +++ b/test/fv/fv_suite_test.go @@ -152,6 +152,12 @@ var _ = SynchronizedBeforeSuite(func() []byte { return []byte{} }, func(data []byte) { + // SynchronizedBeforeSuite's first function above only runs on process #1; this second + // function runs on every parallel process, so it needs its own SetLogger call too, + // otherwise any other process's first client.New below warns "log.SetLogger(...) was + // never called". + ctrl.SetLogger(klog.Background()) + restConfig := ctrl.GetConfigOrDie() // To get rid of the annoying request.go log restConfig.QPS = 100 diff --git a/test/pullmode-sveltosapplier.yaml b/test/pullmode-sveltosapplier.yaml index aa61488..429ba39 100644 --- a/test/pullmode-sveltosapplier.yaml +++ b/test/pullmode-sveltosapplier.yaml @@ -100,7 +100,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-applier@sha256:6bea4f06b8d1d3fa988b018045727c715fce63d3d46b143e6b392aefc062e828 + image: docker.io/projectsveltos/sveltos-applier@sha256:942a429b7d7ffbb5b598a26230454b29c9a8b3cf904fef1941594c0e26b04b09 livenessProbe: failureThreshold: 3 httpGet: