mirror of https://github.com/grafana/grafana
Provisioning: Add API endpoint to reload provisioning configs (#16579)
* Add api to reaload provisioning * Refactor and simplify the polling code * Add test for the provisioning service * Fix provider initialization and move some code to file reader * Simplify the code and move initialization * Remove unused code * Update comment * Add comment * Change error messages * Add DashboardProvisionerFactory type * Update imports * Use new assert lib * Use mutext for synchronizing the reloading * Fix typo Co-Authored-By: aocenas <mr.ocenas@gmail.com> * Add docs about the new apipull/16764/head
parent
b3bfbc6f77
commit
42b745a098
@ -0,0 +1,30 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
) |
||||
|
||||
func (server *HTTPServer) AdminProvisioningReloadDasboards(c *models.ReqContext) Response { |
||||
err := server.ProvisioningService.ProvisionDashboards() |
||||
if err != nil && err != context.Canceled { |
||||
return Error(500, "", err) |
||||
} |
||||
return Success("Dashboards config reloaded") |
||||
} |
||||
|
||||
func (server *HTTPServer) AdminProvisioningReloadDatasources(c *models.ReqContext) Response { |
||||
err := server.ProvisioningService.ProvisionDatasources() |
||||
if err != nil { |
||||
return Error(500, "", err) |
||||
} |
||||
return Success("Datasources config reloaded") |
||||
} |
||||
|
||||
func (server *HTTPServer) AdminProvisioningReloadNotifications(c *models.ReqContext) Response { |
||||
err := server.ProvisioningService.ProvisionNotifications() |
||||
if err != nil { |
||||
return Error(500, "", err) |
||||
} |
||||
return Success("Notifications config reloaded") |
||||
} |
@ -0,0 +1,36 @@ |
||||
package dashboards |
||||
|
||||
import "context" |
||||
|
||||
type Calls struct { |
||||
Provision []interface{} |
||||
PollChanges []interface{} |
||||
} |
||||
|
||||
type DashboardProvisionerMock struct { |
||||
Calls *Calls |
||||
ProvisionFunc func() error |
||||
PollChangesFunc func(ctx context.Context) |
||||
} |
||||
|
||||
func NewDashboardProvisionerMock() *DashboardProvisionerMock { |
||||
return &DashboardProvisionerMock{ |
||||
Calls: &Calls{}, |
||||
} |
||||
} |
||||
|
||||
func (dpm *DashboardProvisionerMock) Provision() error { |
||||
dpm.Calls.Provision = append(dpm.Calls.Provision, nil) |
||||
if dpm.ProvisionFunc != nil { |
||||
return dpm.ProvisionFunc() |
||||
} else { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func (dpm *DashboardProvisionerMock) PollChanges(ctx context.Context) { |
||||
dpm.Calls.PollChanges = append(dpm.Calls.PollChanges, ctx) |
||||
if dpm.PollChangesFunc != nil { |
||||
dpm.PollChangesFunc(ctx) |
||||
} |
||||
} |
@ -0,0 +1,90 @@ |
||||
package provisioning |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"github.com/grafana/grafana/pkg/services/provisioning/dashboards" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/stretchr/testify/assert" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestProvisioningServiceImpl(t *testing.T) { |
||||
t.Run("Restart dashboard provisioning and stop service", func(t *testing.T) { |
||||
service, mock := setup() |
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
var serviceRunning bool |
||||
var serviceError error |
||||
|
||||
err := service.ProvisionDashboards() |
||||
assert.Nil(t, err) |
||||
go func() { |
||||
serviceRunning = true |
||||
serviceError = service.Run(ctx) |
||||
serviceRunning = false |
||||
}() |
||||
time.Sleep(time.Millisecond) |
||||
assert.Equal(t, 1, len(mock.Calls.PollChanges), "PollChanges should have been called") |
||||
|
||||
err = service.ProvisionDashboards() |
||||
assert.Nil(t, err) |
||||
time.Sleep(time.Millisecond) |
||||
assert.Equal(t, 2, len(mock.Calls.PollChanges), "PollChanges should have been called 2 times") |
||||
|
||||
pollingCtx := mock.Calls.PollChanges[0].(context.Context) |
||||
assert.Equal(t, context.Canceled, pollingCtx.Err(), "Polling context from first call should have been cancelled") |
||||
assert.True(t, serviceRunning, "Service should be still running") |
||||
|
||||
// Cancelling the root context and stopping the service
|
||||
cancel() |
||||
time.Sleep(time.Millisecond) |
||||
|
||||
assert.False(t, serviceRunning, "Service should not be running") |
||||
assert.Equal(t, context.Canceled, serviceError, "Service should have returned canceled error") |
||||
|
||||
}) |
||||
|
||||
t.Run("Failed reloading does not stop polling with old provisioned", func(t *testing.T) { |
||||
service, mock := setup() |
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
var serviceRunning bool |
||||
|
||||
err := service.ProvisionDashboards() |
||||
assert.Nil(t, err) |
||||
go func() { |
||||
serviceRunning = true |
||||
_ = service.Run(ctx) |
||||
serviceRunning = false |
||||
}() |
||||
time.Sleep(time.Millisecond) |
||||
assert.Equal(t, 1, len(mock.Calls.PollChanges), "PollChanges should have been called") |
||||
|
||||
mock.ProvisionFunc = func() error { |
||||
return errors.New("Test error") |
||||
} |
||||
err = service.ProvisionDashboards() |
||||
assert.NotNil(t, err) |
||||
time.Sleep(time.Millisecond) |
||||
// This should have been called with the old provisioner, after the last one failed.
|
||||
assert.Equal(t, 2, len(mock.Calls.PollChanges), "PollChanges should have been called 2 times") |
||||
assert.True(t, serviceRunning, "Service should be still running") |
||||
|
||||
// Cancelling the root context and stopping the service
|
||||
cancel() |
||||
|
||||
}) |
||||
} |
||||
|
||||
func setup() (*provisioningServiceImpl, *dashboards.DashboardProvisionerMock) { |
||||
dashMock := dashboards.NewDashboardProvisionerMock() |
||||
service := NewProvisioningServiceImpl( |
||||
func(path string) (dashboards.DashboardProvisioner, error) { |
||||
return dashMock, nil |
||||
}, |
||||
nil, |
||||
nil, |
||||
) |
||||
service.Cfg = setting.NewCfg() |
||||
return service, dashMock |
||||
} |
Loading…
Reference in new issue