Provisioning: Add integration test for RV conflicts (#111790)

This commit is contained in:
Stephanie Hingtgen 2025-09-30 08:48:53 -06:00 committed by GitHub
parent 90da925985
commit 96407ef46b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 91 additions and 0 deletions

View File

@ -0,0 +1,83 @@
package provisioning
import (
"context"
"testing"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/util/testutil"
)
// TestIntegrationProvisioning_JobConflict tests that if two concurrent drivers try to update a
// job they received before the other updated it, that one will fail. This is critical for concurrent jobs
func TestIntegrationProvisioning_JobConflict(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
// disable the controllers so the jobs don't get auto-processed
helper := runGrafana(t, func(opts *testinfra.GrafanaOpts) {
opts.DisableProvisioningControllers = true
})
ctx := context.Background()
// create a job
obj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Job",
"metadata": map[string]interface{}{
"name": "test-job-conflict",
"namespace": "default",
},
"spec": map[string]interface{}{
"action": string(provisioning.JobActionPull),
"repository": "test-repo",
"pull": map[string]interface{}{
"incremental": false,
},
},
},
}
createdJob, err := helper.Jobs.Resource.Create(ctx, obj, metav1.CreateOptions{})
require.NoError(t, err)
// have two clients get the same job before either has updated it. this simulates the race condition
// between two concurrent workers.
job, err := helper.Jobs.Resource.Get(ctx, createdJob.GetName(), metav1.GetOptions{})
require.NoError(t, err)
job2, err := helper.Jobs.Resource.Get(ctx, createdJob.GetName(), metav1.GetOptions{})
require.NoError(t, err)
// have the first client update the job, this should update the RV
client1Update := job.DeepCopy()
if client1Update.GetLabels() == nil {
client1Update.SetLabels(make(map[string]string))
}
labels := client1Update.GetLabels()
labels["provisioning.grafana.app/claim"] = "client1-claim"
client1Update.SetLabels(labels)
updatedJob, err := helper.Jobs.Resource.Update(ctx, client1Update, metav1.UpdateOptions{})
require.NoError(t, err)
require.NotEqual(t, job.GetResourceVersion(), updatedJob.GetResourceVersion())
// now when client two tries to update the job, the RV is no longer what it originally received
client2Update := job2.DeepCopy()
if client2Update.GetLabels() == nil {
client2Update.SetLabels(make(map[string]string))
}
labels2 := client2Update.GetLabels()
labels2["provisioning.grafana.app/claim"] = "client2-claim"
client2Update.SetLabels(labels2)
_, err = helper.Jobs.Resource.Update(ctx, client2Update, metav1.UpdateOptions{})
require.Error(t, err)
require.True(t, apierrors.IsConflict(err), "should get conflict error when updating with stale resource version")
// clean up
err = helper.Jobs.Resource.Delete(ctx, createdJob.GetName(), metav1.DeleteOptions{})
require.NoError(t, err)
}

View File

@ -545,6 +545,13 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) {
require.NoError(t, err)
}
if opts.DisableProvisioningControllers {
provisioningSection, err := getOrCreateSection("provisioning")
require.NoError(t, err)
_, err = provisioningSection.NewKey("disable_controllers", "true")
require.NoError(t, err)
}
dashboardsSection, err := getOrCreateSection("dashboards")
require.NoError(t, err)
_, err = dashboardsSection.NewKey("min_refresh_interval", "10s")
@ -615,6 +622,7 @@ type GrafanaOpts struct {
EnableRecordingRules bool
EnableSCIM bool
APIServerRuntimeConfig string
DisableProvisioningControllers bool
// When "unified-grpc" is selected it will also start the grpc server
APIServerStorageType options.StorageType