Conversation
7b569bb to
a7fc951
Compare
840964f to
9e5ab0f
Compare
f41gh7
left a comment
There was a problem hiding this comment.
It's required to add documentation in first place. Is it possible that model could interfere on each other? Is it required to config VMAnomaly itself somehow? What is a relations between VMAnomaly and VMAnomalyModel?
Currently it's not possible to use this new API.
Also, I suggest to add -configCheckInterval to the vmanomaly in the same way as other components have it. It solves configuration reload without need of external config reloaders.
|
removed config reloader since python watchdog, which implements hot reload listens to fs events |
0ec9f87 to
1ba5e13
Compare
1ba5e13 to
4558e7c
Compare
|
added scheduler CR, added docs |
1b8eb91 to
2216f0a
Compare
2216f0a to
d0a06c0
Compare
66eac73 to
bfc5203
Compare
cdea53b to
5914c18
Compare
30c4e13 to
96dc1df
Compare
96dc1df to
9d58b90
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
9d58b90 to
11c333c
Compare
11c333c to
bf10354
Compare
c53d6a0 to
21aa85c
Compare
There was a problem hiding this comment.
8 issues found across 31 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/controller/operator/vmanomalyconfig_controller_test.go">
<violation number="1" location="internal/controller/operator/vmanomalyconfig_controller_test.go:47">
P2: Fail fast on non-NotFound errors in test setup instead of silently ignoring them.</violation>
</file>
<file name="docs/CHANGELOG.md">
<violation number="1" location="docs/CHANGELOG.md:34">
P1: Custom agent: **Changelog Review Agent**
This entry is missing the mandatory issue/PR reference link required by the changelog structure.</violation>
<violation number="2" location="docs/CHANGELOG.md:223">
P2: This changelog entry is duplicated and appears in the wrong release section (`v0.64.0` and `v0.65.0`), which makes release notes inaccurate.</violation>
<violation number="3" location="docs/CHANGELOG.md:223">
P1: Custom agent: **Changelog Review Agent**
New changelog entries must be added only to `tip`; this line adds a new BUGFIX entry in a released section, which violates placement requirements.</violation>
</file>
<file name="internal/controller/operator/vmanomalyconfig_controller.go">
<violation number="1" location="internal/controller/operator/vmanomalyconfig_controller.go:118">
P2: Propagate the CreateOrUpdateConfig error so reconcile fails and requeues instead of silently succeeding.</violation>
</file>
<file name="internal/controller/operator/factory/vmanomaly/config/config.go">
<violation number="1" location="internal/controller/operator/factory/vmanomaly/config/config.go:288">
P1: Writing merged model entries into `c.Models` can panic when the base config omits `models` (nil map). Initialize the map before assignment.</violation>
<violation number="2" location="internal/controller/operator/factory/vmanomaly/config/config.go:292">
P1: Writing merged scheduler entries into `c.Schedulers` can panic when the base config omits `schedulers` (nil map). Initialize the map before assignment.</violation>
</file>
<file name="internal/controller/operator/factory/vmanomaly/config/config_test.go">
<violation number="1" location="internal/controller/operator/factory/vmanomaly/config/config_test.go:714">
P2: The new external-config test sets `ConfigNamespaceSelector` but doesn't create a matching labeled Namespace object, so no `VMAnomalyConfig` can be selected in this setup.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| BeforeEach(func() { | ||
| By("creating the custom resource for the Kind VMAnomalyConfig") | ||
| err := k8sClient.Get(ctx, typeNamespacedName, vmanomalyconfig) | ||
| if err != nil && k8serrors.IsNotFound(err) { |
There was a problem hiding this comment.
P2: Fail fast on non-NotFound errors in test setup instead of silently ignoring them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/controller/operator/vmanomalyconfig_controller_test.go, line 47:
<comment>Fail fast on non-NotFound errors in test setup instead of silently ignoring them.</comment>
<file context>
@@ -0,0 +1,83 @@
+ BeforeEach(func() {
+ By("creating the custom resource for the Kind VMAnomalyConfig")
+ err := k8sClient.Get(ctx, typeNamespacedName, vmanomalyconfig)
+ if err != nil && k8serrors.IsNotFound(err) {
+ resource := &vmv1.VMAnomalyConfig{
+ ObjectMeta: metav1.ObjectMeta{
</file context>
| } | ||
| } | ||
|
|
||
| if err := vmanomaly.CreateOrUpdateConfig(ctx, r, item, &instance); err != nil { |
There was a problem hiding this comment.
P2: Propagate the CreateOrUpdateConfig error so reconcile fails and requeues instead of silently succeeding.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/controller/operator/vmanomalyconfig_controller.go, line 118:
<comment>Propagate the CreateOrUpdateConfig error so reconcile fails and requeues instead of silently succeeding.</comment>
<file context>
@@ -0,0 +1,136 @@
+ }
+ }
+
+ if err := vmanomaly.CreateOrUpdateConfig(ctx, r, item, &instance); err != nil {
+ l.Error(err, "failed to update vmanomaly config")
+ }
</file context>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 31 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| parentObject := fmt.Sprintf("%s.%s.vmanomaly", cr.Name, cr.Namespace) | ||
| return reconcile.StatusForChildObjects(ctx, rclient, parentObject, []*vmv1.VMAnomalyConfig{childObject}) |
There was a problem hiding this comment.
UpdateStatusesForChildObjects updates status using the childObject pointer passed by the caller. Since ParsedObjects.selectConfigs builds its own object instances, validation errors recorded via ChildObjects.ForEachCollectSkipInvalid won't be reflected in this childObject, so the condition may be marked applied even when this config was rejected (collision/unmarshal/etc). Consider looking up the matching object via pos.configs.Get(childObject) (and skipping update if it isn't selected) or updating statuses for pos.configs.All() when appropriate.
| parentObject := fmt.Sprintf("%s.%s.vmanomaly", cr.Name, cr.Namespace) | |
| return reconcile.StatusForChildObjects(ctx, rclient, parentObject, []*vmv1.VMAnomalyConfig{childObject}) | |
| selectedChildObject := pos.configs.Get(childObject) | |
| if selectedChildObject == nil { | |
| return nil | |
| } | |
| parentObject := fmt.Sprintf("%s.%s.vmanomaly", cr.Name, cr.Namespace) | |
| return reconcile.StatusForChildObjects(ctx, rclient, parentObject, []*vmv1.VMAnomalyConfig{selectedChildObject}) |
| func (r *VMAnomalyConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
| return ctrl.NewControllerManagedBy(mgr). | ||
| For(&vmv1.VMAnomalyConfig{}). | ||
| WithEventFilter(predicate.TypedGenerationChangedPredicate[client.Object]{}). | ||
| WithOptions(getDefaultOptions()). | ||
| Complete(r) |
There was a problem hiding this comment.
SetupWithManager uses GenerationChangedPredicate, but this controller's Reconcile() contains deletion-specific behavior based on DeletionTimestamp. DeletionTimestamp updates do not change generation, so the reconcile that should remove the config from VMAnomaly instances may never run. Use a predicate that allows DeletionTimestamp/resourceVersion changes (or add a finalizer and react to the finalizer update) so delete flows are handled before the object disappears.
| if err := vmanomaly.CreateOrUpdateConfig(ctx, r, item, &instance); err != nil { | ||
| l.Error(err, "failed to update vmanomaly config") | ||
| } | ||
| } |
There was a problem hiding this comment.
Errors from vmanomaly.CreateOrUpdateConfig are only logged and then ignored, so the reconcile returns success even if updating VMAnomaly instances fails (no retry/requeue). Consider aggregating these errors (or returning the first error) so controller-runtime will requeue and the failure is visible.
| The `VMAnomalyConfig` CRD allows to declaratively define anomaly detection [models](https://docs.victoriametrics.com/anomaly-detection/components/models/). [schedulers](https://docs.victoriametrics.com/anomaly-detection/components/schedulers/) and [queries](https://docs.victoriametrics.com/anomaly-detection/components/reader/#per-query-parameters). | ||
|
|
||
| `VMAnomalyConfig` object updates `models`, `schedulers` and `reader.queries` sections of [VMAnomaly](https://docs.victoriametrics.com/anomaly-detection/) | ||
| configuration by adding items with `{metadata.namespace}-{metadata.name}` key prefix. Whole VMAnomalyConfig fails if at least one item collides: |
There was a problem hiding this comment.
The doc claims "Whole VMAnomalyConfig fails if at least one item collides", but the current implementation uses ChildObjects.ForEachCollectSkipInvalid (skips invalid configs and continues generating the main config). Either adjust the implementation to fail hard on collisions, or update the documentation to reflect the skip-with-status behavior so users know what to expect.
| configuration by adding items with `{metadata.namespace}-{metadata.name}` key prefix. Whole VMAnomalyConfig fails if at least one item collides: | |
| configuration by adding items with `{metadata.namespace}-{metadata.name}` key prefix. If at least one generated item collides with an existing key, only the colliding item is skipped, while other valid items from the same `VMAnomalyConfig` are still added to the resulting configuration. Check the `VMAnomalyConfig` status and related events to identify skipped items caused by collisions. |
| "context" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| k8serrors "k8s.io/apimachinery/pkg/api/errors" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
|
|
||
| vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1" | ||
| ) | ||
|
|
||
| var _ = Describe("VMAnomalyConfig Controller", func() { | ||
| Context("When reconciling a resource", func() { | ||
| const resourceName = "test-resource" | ||
|
|
||
| ctx := context.Background() | ||
|
|
||
| typeNamespacedName := types.NamespacedName{ | ||
| Name: resourceName, | ||
| Namespace: "default", // TODO(user):Modify as needed | ||
| } | ||
| vmanomalyconfig := &vmv1.VMAnomalyConfig{} | ||
|
|
||
| BeforeEach(func() { | ||
| By("creating the custom resource for the Kind VMAnomalyConfig") | ||
| err := k8sClient.Get(ctx, typeNamespacedName, vmanomalyconfig) | ||
| if err != nil && k8serrors.IsNotFound(err) { | ||
| resource := &vmv1.VMAnomalyConfig{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: resourceName, | ||
| Namespace: "default", | ||
| }, | ||
| // TODO(user): Specify other spec details if needed. | ||
| } | ||
| Expect(k8sClient.Create(ctx, resource)).To(Succeed()) | ||
| } | ||
| }) | ||
|
|
||
| AfterEach(func() { | ||
| // TODO(user): Cleanup logic after each test, like removing the resource instance. | ||
| resource := &vmv1.VMAnomalyConfig{} | ||
| err := k8sClient.Get(ctx, typeNamespacedName, resource) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| By("Cleanup the specific resource instance VMAnomalyConfig") | ||
| Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) | ||
| }) | ||
| It("should successfully reconcile the resource", func() { | ||
| By("Reconciling the created resource") | ||
| controllerReconciler := &VMAnomalyConfigReconciler{ | ||
| Client: k8sClient, | ||
| OriginScheme: k8sClient.Scheme(), | ||
| } | ||
|
|
||
| _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ | ||
| NamespacedName: typeNamespacedName, | ||
| }) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. | ||
| // Example: If you expect a certain status condition after reconciliation, verify it here. | ||
| }) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
This new controller test is kubebuilder scaffold with TODOs and no assertions about reconciliation side-effects, so it doesn't meaningfully cover the new behavior. Either implement real assertions (e.g., create VMAnomaly + VMAnomalyConfig and verify the generated Secret/status updates), or remove the scaffold test to avoid giving a false sense of coverage.
| "context" | |
| . "github.com/onsi/ginkgo/v2" | |
| . "github.com/onsi/gomega" | |
| k8serrors "k8s.io/apimachinery/pkg/api/errors" | |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |
| "k8s.io/apimachinery/pkg/types" | |
| "sigs.k8s.io/controller-runtime/pkg/reconcile" | |
| vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1" | |
| ) | |
| var _ = Describe("VMAnomalyConfig Controller", func() { | |
| Context("When reconciling a resource", func() { | |
| const resourceName = "test-resource" | |
| ctx := context.Background() | |
| typeNamespacedName := types.NamespacedName{ | |
| Name: resourceName, | |
| Namespace: "default", // TODO(user):Modify as needed | |
| } | |
| vmanomalyconfig := &vmv1.VMAnomalyConfig{} | |
| BeforeEach(func() { | |
| By("creating the custom resource for the Kind VMAnomalyConfig") | |
| err := k8sClient.Get(ctx, typeNamespacedName, vmanomalyconfig) | |
| if err != nil && k8serrors.IsNotFound(err) { | |
| resource := &vmv1.VMAnomalyConfig{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Name: resourceName, | |
| Namespace: "default", | |
| }, | |
| // TODO(user): Specify other spec details if needed. | |
| } | |
| Expect(k8sClient.Create(ctx, resource)).To(Succeed()) | |
| } | |
| }) | |
| AfterEach(func() { | |
| // TODO(user): Cleanup logic after each test, like removing the resource instance. | |
| resource := &vmv1.VMAnomalyConfig{} | |
| err := k8sClient.Get(ctx, typeNamespacedName, resource) | |
| Expect(err).NotTo(HaveOccurred()) | |
| By("Cleanup the specific resource instance VMAnomalyConfig") | |
| Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) | |
| }) | |
| It("should successfully reconcile the resource", func() { | |
| By("Reconciling the created resource") | |
| controllerReconciler := &VMAnomalyConfigReconciler{ | |
| Client: k8sClient, | |
| OriginScheme: k8sClient.Scheme(), | |
| } | |
| _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ | |
| NamespacedName: typeNamespacedName, | |
| }) | |
| Expect(err).NotTo(HaveOccurred()) | |
| // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. | |
| // Example: If you expect a certain status condition after reconciliation, verify it here. | |
| }) | |
| }) | |
| }) | |
| . "github.com/onsi/ginkgo/v2" | |
| ) | |
| // The autogenerated kubebuilder scaffold test for VMAnomalyConfig was removed | |
| // because it only asserted that Reconcile returned no error and did not verify | |
| // any reconciliation side-effects. Add a controller test here once concrete | |
| // assertions can be made against observable behavior. |
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docs/CHANGELOG.md">
<violation number="1" location="docs/CHANGELOG.md:20">
P1: Custom agent: **Changelog Review Agent**
This new changelog item is non-compliant with the required changelog format: it is missing the mandatory `FEATURE|BUGFIX|SECURITY` prefix, affected services, and reference links.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
There was a problem hiding this comment.
Do we want to run it through v1beta1 for a few releases first?
| for i := range list.Items { | ||
| item := &list.Items[i] |
There was a problem hiding this comment.
| for i := range list.Items { | |
| item := &list.Items[i] | |
| for _, item := range list.Items { |
Since we're not mutating item here
There was a problem hiding this comment.
we're mutating them later in ForEach loops
| case cr.Spec.ConfigRawYaml != "": | ||
| data = []byte(cr.Spec.ConfigRawYaml) | ||
| default: | ||
| return nil, fmt.Errorf(`either "configRawYaml" or "configSecret" are required`) |
There was a problem hiding this comment.
We should do this check via validatingwebhook. Also, I'd expect raw yaml to take precedence when both are specified?
There was a problem hiding this comment.
vmalertmanager has the same properties and priorities are the same
| ) | ||
|
|
||
| func reloadSupported(cr *vmv1.VMAnomaly) bool { | ||
| anomalyVersion, err := semver.NewVersion(cr.Spec.Image.Tag) |
There was a problem hiding this comment.
This might break if customers use custom tags / RCs - we should add a CHANGELOG entry for this
There was a problem hiding this comment.
rcs should be covered by this check, but custom builds - not, haven't seen Fred shares builds with nonstandard versions
There was a problem hiding this comment.
Okay, lets mention it in the changelog - there is a chance the customers may mirror the images in their org and having this mentioned may save us some time on issue troubleshooting
| } | ||
|
|
||
| if err := vmanomaly.CreateOrUpdateConfig(ctx, r, item, &instance); err != nil { | ||
| l.Error(err, "failed to update vmanomaly config") |
There was a problem hiding this comment.
| l.Error(err, "failed to update vmanomaly config") | |
| l.Error(err, "failed to update vmanomaly config") | |
| return |
There was a problem hiding this comment.
problem with this change is that error can be caused by other vmanomalyconfig
| Name: resourceName, | ||
| Namespace: "default", | ||
| }, | ||
| // TODO(user): Specify other spec details if needed. |
There was a problem hiding this comment.
this is autogenerated file
| NamespacedName: typeNamespacedName, | ||
| }) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. |
There was a problem hiding this comment.
We can extend the e2e tests later
Co-authored-by: Vadim Rutkovsky <vadim@vrutkovs.eu> Signed-off-by: Andrii Chubatiuk <andrew.chubatiuk@gmail.com>
Co-authored-by: Vadim Rutkovsky <vadim@vrutkovs.eu> Signed-off-by: Andrii Chubatiuk <andrew.chubatiuk@gmail.com>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docs/CHANGELOG.md">
<violation number="1" location="docs/CHANGELOG.md:22">
P3: Use a plural verb in this release note sentence ("were introduced" instead of "was introduced").</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
Co-authored-by: Vadim Rutkovsky <vadim@vrutkovs.eu> Signed-off-by: Andrii Chubatiuk <andrew.chubatiuk@gmail.com>
Signed-off-by: Vadim Rutkovsky <vadim@vrutkovs.eu>
Co-authored-by: Vadim Rutkovsky <vadim@vrutkovs.eu> Signed-off-by: Andrii Chubatiuk <andrew.chubatiuk@gmail.com>
starting 1.25.0 anomaly supports hot-reload, and this PR introduces VMAnomalyConfig CR, that allows to add extra
models,schedulersandqueriesfor VMAnomaly dynamic configuration