Provisioning: Add Standalone Job Controller Without Job Processing (#109610)
* Add standalone job controller * Add makefile * Add limit on the current implementation * Move job controllers to app package * Add TLS flags
This commit is contained in:
@@ -34,7 +34,7 @@ require (
|
||||
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/elazarl/goproxy v1.7.2 // indirect
|
||||
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
|
||||
|
||||
@@ -40,8 +40,9 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ replace github.com/docker/docker => github.com/moby/moby v27.5.1+incompatible
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect; @grafana/grafana-backend-group
|
||||
github.com/urfave/cli/v2 v2.27.6 // @grafana/grafana-backend-group
|
||||
github.com/urfave/cli/v2 v2.27.7 // @grafana/grafana-backend-group
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect; @grafana/grafana-backend-group
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect; @grafana/grafana-backend-group
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect; @grafana/grafana-backend-group
|
||||
@@ -21,7 +21,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect; @grafana/grafana-app-platform-squad
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
|
||||
+4
-4
@@ -12,8 +12,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -51,8 +51,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
|
||||
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
|
||||
github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
|
||||
github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dennwc/varint v1.0.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.2 // indirect
|
||||
|
||||
+2
-2
@@ -49,8 +49,8 @@ github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvP
|
||||
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
|
||||
informer "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
)
|
||||
|
||||
const (
|
||||
historyJobControllerLoggerName = "provisioning-historyjob-controller"
|
||||
)
|
||||
|
||||
// HistoryJobController manages the cleanup of old HistoryJob entries.
|
||||
type HistoryJobController struct {
|
||||
client client.ProvisioningV0alpha1Interface
|
||||
logger logging.Logger
|
||||
expirationTime time.Duration
|
||||
}
|
||||
|
||||
// NewHistoryJobController creates a new HistoryJobController.
|
||||
func NewHistoryJobController(
|
||||
provisioningClient client.ProvisioningV0alpha1Interface,
|
||||
historyJobInformer informer.HistoricJobInformer,
|
||||
expirationTime time.Duration,
|
||||
) (*HistoryJobController, error) {
|
||||
c := &HistoryJobController{
|
||||
client: provisioningClient,
|
||||
logger: logging.DefaultLogger.With("logger", historyJobControllerLoggerName),
|
||||
expirationTime: expirationTime,
|
||||
}
|
||||
|
||||
// Use the resync events from the shared informer to trigger cleanup for each job
|
||||
_, err := historyJobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
c.cleanupJob(obj)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
c.cleanupJob(newObj)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *HistoryJobController) cleanupJob(obj interface{}) {
|
||||
job, ok := obj.(*provisioning.HistoricJob)
|
||||
if !ok {
|
||||
c.logger.Error("Expected HistoricJob but got", "type", obj)
|
||||
return
|
||||
}
|
||||
|
||||
age := time.Since(job.CreationTimestamp.Time)
|
||||
if age > c.expirationTime {
|
||||
namespace := job.Namespace
|
||||
ctx, _, err := identity.WithProvisioningIdentity(context.Background(), namespace)
|
||||
if err != nil {
|
||||
c.logger.Error("Failed to set provisioning identity for cleanup", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
err = c.client.HistoricJobs(job.Namespace).Delete(ctx, job.Name, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
c.logger.Error("Failed to delete expired HistoryJob",
|
||||
"namespace", job.Namespace,
|
||||
"name", job.Name,
|
||||
"age", age,
|
||||
"error", err)
|
||||
} else {
|
||||
c.logger.Info("Deleted expired HistoryJob",
|
||||
"namespace", job.Namespace,
|
||||
"name", job.Name,
|
||||
"age", age)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
informer "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
jobControllerLoggerName = "provisioning-job-controller"
|
||||
)
|
||||
|
||||
// JobController manages job create notifications.
|
||||
type JobController struct {
|
||||
jobSynced cache.InformerSynced
|
||||
logger logging.Logger
|
||||
|
||||
// notification channel for job create events (replaces InsertNotifications)
|
||||
notifications chan struct{}
|
||||
}
|
||||
|
||||
// NewJobController creates a new JobController.
|
||||
func NewJobController(
|
||||
jobInformer informer.JobInformer,
|
||||
) (*JobController, error) {
|
||||
jc := &JobController{
|
||||
jobSynced: jobInformer.Informer().HasSynced,
|
||||
logger: logging.DefaultLogger.With("logger", jobControllerLoggerName),
|
||||
notifications: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
_, err := jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
// Send notification for job create events (replaces InsertNotifications)
|
||||
jc.sendNotification()
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jc, nil
|
||||
}
|
||||
|
||||
// InsertNotifications returns a channel that receives notifications when jobs are created.
|
||||
// This replaces the InsertNotifications method from persistentstore.go.
|
||||
func (jc *JobController) InsertNotifications() chan struct{} {
|
||||
return jc.notifications
|
||||
}
|
||||
|
||||
func (jc *JobController) sendNotification() {
|
||||
select {
|
||||
case jc.notifications <- struct{}{}:
|
||||
default:
|
||||
// Don't block if there's already a notification waiting
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
provisioningfake "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/fake"
|
||||
provisioninginformers "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions"
|
||||
)
|
||||
|
||||
func TestJobController_New(t *testing.T) {
|
||||
client := provisioningfake.NewSimpleClientset()
|
||||
informerFactory := provisioninginformers.NewSharedInformerFactory(client, time.Second)
|
||||
jobInformer := informerFactory.Provisioning().V0alpha1().Jobs()
|
||||
|
||||
controller, err := NewJobController(jobInformer)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, controller)
|
||||
assert.NotNil(t, controller.notifications)
|
||||
}
|
||||
|
||||
func TestJobController_InsertNotifications(t *testing.T) {
|
||||
client := provisioningfake.NewSimpleClientset()
|
||||
informerFactory := provisioninginformers.NewSharedInformerFactory(client, time.Second)
|
||||
jobInformer := informerFactory.Provisioning().V0alpha1().Jobs()
|
||||
|
||||
controller, err := NewJobController(jobInformer)
|
||||
require.NoError(t, err)
|
||||
|
||||
notifications := controller.InsertNotifications()
|
||||
assert.NotNil(t, notifications)
|
||||
|
||||
// Test that notification is sent
|
||||
controller.sendNotification()
|
||||
|
||||
select {
|
||||
case <-notifications:
|
||||
// Success - notification received
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Expected notification but didn't receive one")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobController_NotificationOnJobCreate(t *testing.T) {
|
||||
client := provisioningfake.NewSimpleClientset()
|
||||
informerFactory := provisioninginformers.NewSharedInformerFactory(client, time.Second)
|
||||
jobInformer := informerFactory.Provisioning().V0alpha1().Jobs()
|
||||
|
||||
controller, err := NewJobController(jobInformer)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start informer and wait for cache sync
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
informerFactory.Start(ctx.Done())
|
||||
informerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
// Get notifications channel
|
||||
notifications := controller.InsertNotifications()
|
||||
|
||||
// Create a job - this should trigger a notification
|
||||
_, err = client.ProvisioningV0alpha1().Jobs("default").Create(ctx, &provisioning.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-job",
|
||||
Namespace: "default",
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for notification
|
||||
select {
|
||||
case <-notifications:
|
||||
// Success - notification received
|
||||
case <-time.After(time.Second * 2):
|
||||
t.Fatal("Expected notification but didn't receive one")
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
|
||||
|
||||
appcontroller "github.com/grafana/grafana/apps/provisioning/pkg/controller"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||
deletepkg "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/delete"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/export"
|
||||
@@ -744,7 +746,7 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH
|
||||
}
|
||||
|
||||
// Create JobController to handle job create notifications
|
||||
jobController, err := controller.NewJobController(jobInformer)
|
||||
jobController, err := appcontroller.NewJobController(jobInformer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -809,7 +811,7 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH
|
||||
historyJobInformerFactory := informers.NewSharedInformerFactory(c, historyJobExpiration)
|
||||
historyJobInformer := historyJobInformerFactory.Provisioning().V0alpha1().HistoricJobs()
|
||||
go historyJobInformer.Informer().Run(postStartHookCtx.Done())
|
||||
_, err = controller.NewHistoryJobController(
|
||||
_, err = appcontroller.NewHistoryJobController(
|
||||
b.GetClient(),
|
||||
historyJobInformer,
|
||||
historyJobExpiration,
|
||||
|
||||
Reference in New Issue
Block a user