mirror of https://github.com/grafana/grafana
Correlations: Add CreateCorrelation HTTP API (#51630)
* Correlations: add migration
* Correlations: Add CreateCorrelation API
* Correlations: Make correlations work with provisioning
* Handle version changes
* Fix lining error
* lint fixes
* rebuild betterer results
* add a UID to each correlation
* Fix lint errors
* add docs
* better wording in API docs
* remove leftover comment
* handle ds updates
* Fix error message typo
* add bad data test
* make correlations a separate table
* skip readonly check when provisioning correlations
* delete stale correlations when datasources are deleted
* restore provisioned readonly ds
* publish deletion event with full data
* generate swagger and HTTP API docs
* apply source datasource permission to create correlation API
* Fix tests & lint errors
* ignore empty deletion events
* fix last lint errors
* fix more lint error
* Only publish deletion event if datasource was actually deleted
* delete DS provisioning deletes correlations, added & fixed tests
* Fix unmarshalling tests
* Fix linting errors
* Fix deltion event tests
* fix small linting error
* fix lint errors
* update betterer
* fix test
* make path singular
* Revert "make path singular"
This reverts commit 420c3d315e.
* add integration tests
* remove unneeded id from correlations table
* update spec
* update leftover references to CorrelationDTO
* fix tests
* cleanup tests
* fix lint error
pull/52735/head
parent
dbc2171401
commit
5ce4baf6f5
@ -0,0 +1,70 @@ |
||||
--- |
||||
aliases: |
||||
- /docs/grafana/latest/developers/http_api/correlations/ |
||||
- /docs/grafana/latest/http_api/correlations/ |
||||
description: Grafana Correlations HTTP API |
||||
keywords: |
||||
- grafana |
||||
- http |
||||
- documentation |
||||
- api |
||||
- correlations |
||||
- Glue |
||||
title: 'Correlations HTTP API ' |
||||
--- |
||||
|
||||
# Correlations API |
||||
|
||||
This API can be used to define correlations between data sources. |
||||
|
||||
## Create correlations |
||||
|
||||
`POST /api/datasources/uid/:sourceUid/correlations` |
||||
|
||||
Creates a correlation between two data sources - the source data source indicated by the path UID, and the target data source which is specified in the body. |
||||
|
||||
**Example request:** |
||||
|
||||
```http |
||||
POST /api/datasources/uid/uyBf2637k/correlations HTTP/1.1 |
||||
Accept: application/json |
||||
Content-Type: application/json |
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk |
||||
{ |
||||
"targetUid": "PDDA8E780A17E7EF1", |
||||
"label": "My Label", |
||||
"description": "Logs to Traces", |
||||
} |
||||
``` |
||||
|
||||
JSON body schema: |
||||
|
||||
- **targetUid** – Target data source uid. |
||||
- **label** – A label for the correlation. |
||||
- **description** – A description for the correlation. |
||||
|
||||
**Example response:** |
||||
|
||||
```http |
||||
HTTP/1.1 200 |
||||
Content-Type: application/json |
||||
{ |
||||
"message": "Correlation created", |
||||
"result": { |
||||
"description": "Logs to Traces", |
||||
"label": "My Label", |
||||
"sourceUid": "uyBf2637k", |
||||
"targetUid": "PDDA8E780A17E7EF1", |
||||
"uid": "50xhMlg9k" |
||||
} |
||||
} |
||||
``` |
||||
|
||||
Status codes: |
||||
|
||||
- **200** – OK |
||||
- **400** - Errors (invalid JSON, missing or invalid fields) |
||||
- **401** – Unauthorized |
||||
- **403** – Forbidden, source data source is read-only |
||||
- **404** – Not found, either source or target data source could not be found |
||||
- **500** – Internal error |
||||
@ -0,0 +1,33 @@ |
||||
package definitions |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/services/correlations" |
||||
) |
||||
|
||||
// swagger:route POST /datasources/uid/{uid}/correlations correlations createCorrelation
|
||||
//
|
||||
// Add correlation.
|
||||
//
|
||||
// Responses:
|
||||
// 200: createCorrelationResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
|
||||
// swagger:parameters createCorrelation
|
||||
type CreateCorrelationParams struct { |
||||
// in:body
|
||||
// required:true
|
||||
Body correlations.CreateCorrelationCommand `json:"body"` |
||||
// in:path
|
||||
// required:true
|
||||
SourceUID string `json:"uid"` |
||||
} |
||||
|
||||
//swagger:response createCorrelationResponse
|
||||
type CreateCorrelationResponse struct { |
||||
// in: body
|
||||
Body correlations.CreateCorrelationResponse `json:"body"` |
||||
} |
||||
@ -0,0 +1,49 @@ |
||||
package correlations |
||||
|
||||
import ( |
||||
"errors" |
||||
"net/http" |
||||
|
||||
"github.com/grafana/grafana/pkg/api/response" |
||||
"github.com/grafana/grafana/pkg/api/routing" |
||||
"github.com/grafana/grafana/pkg/middleware" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol" |
||||
"github.com/grafana/grafana/pkg/services/datasources" |
||||
|
||||
"github.com/grafana/grafana/pkg/web" |
||||
) |
||||
|
||||
func (s *CorrelationsService) registerAPIEndpoints() { |
||||
uidScope := datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":uid")) |
||||
authorize := ac.Middleware(s.AccessControl) |
||||
|
||||
s.RouteRegister.Group("/api/datasources/uid/:uid/correlations", func(entities routing.RouteRegister) { |
||||
entities.Post("/", middleware.ReqSignedIn, authorize(ac.ReqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(s.createHandler)) |
||||
}) |
||||
} |
||||
|
||||
// createHandler handles POST /datasources/uid/:uid/correlations
|
||||
func (s *CorrelationsService) createHandler(c *models.ReqContext) response.Response { |
||||
cmd := CreateCorrelationCommand{} |
||||
if err := web.Bind(c.Req, &cmd); err != nil { |
||||
return response.Error(http.StatusBadRequest, "bad request data", err) |
||||
} |
||||
cmd.SourceUID = web.Params(c.Req)[":uid"] |
||||
cmd.OrgId = c.OrgId |
||||
|
||||
correlation, err := s.CreateCorrelation(c.Req.Context(), cmd) |
||||
if err != nil { |
||||
if errors.Is(err, ErrSourceDataSourceDoesNotExists) || errors.Is(err, ErrTargetDataSourceDoesNotExists) { |
||||
return response.Error(http.StatusNotFound, "Data source not found", err) |
||||
} |
||||
|
||||
if errors.Is(err, ErrSourceDataSourceReadOnly) { |
||||
return response.Error(http.StatusForbidden, "Data source is read only", err) |
||||
} |
||||
|
||||
return response.Error(http.StatusInternalServerError, "Failed to add correlation", err) |
||||
} |
||||
|
||||
return response.JSON(http.StatusOK, CreateCorrelationResponse{Result: correlation, Message: "Correlation created"}) |
||||
} |
||||
@ -0,0 +1,74 @@ |
||||
package correlations |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing" |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/events" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol" |
||||
"github.com/grafana/grafana/pkg/services/datasources" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
) |
||||
|
||||
func ProvideService(sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister, ds datasources.DataSourceService, ac accesscontrol.AccessControl, bus bus.Bus) *CorrelationsService { |
||||
s := &CorrelationsService{ |
||||
SQLStore: sqlStore, |
||||
RouteRegister: routeRegister, |
||||
log: log.New("correlations"), |
||||
DataSourceService: ds, |
||||
AccessControl: ac, |
||||
} |
||||
|
||||
s.registerAPIEndpoints() |
||||
|
||||
bus.AddEventListener(s.handleDatasourceDeletion) |
||||
|
||||
return s |
||||
} |
||||
|
||||
type Service interface { |
||||
CreateCorrelation(ctx context.Context, cmd CreateCorrelationCommand) (Correlation, error) |
||||
DeleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error |
||||
DeleteCorrelationsByTargetUID(ctx context.Context, cmd DeleteCorrelationsByTargetUIDCommand) error |
||||
} |
||||
|
||||
type CorrelationsService struct { |
||||
SQLStore *sqlstore.SQLStore |
||||
RouteRegister routing.RouteRegister |
||||
log log.Logger |
||||
DataSourceService datasources.DataSourceService |
||||
AccessControl accesscontrol.AccessControl |
||||
} |
||||
|
||||
func (s CorrelationsService) CreateCorrelation(ctx context.Context, cmd CreateCorrelationCommand) (Correlation, error) { |
||||
return s.createCorrelation(ctx, cmd) |
||||
} |
||||
|
||||
func (s CorrelationsService) DeleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error { |
||||
return s.deleteCorrelationsBySourceUID(ctx, cmd) |
||||
} |
||||
|
||||
func (s CorrelationsService) DeleteCorrelationsByTargetUID(ctx context.Context, cmd DeleteCorrelationsByTargetUIDCommand) error { |
||||
return s.deleteCorrelationsByTargetUID(ctx, cmd) |
||||
} |
||||
|
||||
func (s CorrelationsService) handleDatasourceDeletion(ctx context.Context, event *events.DataSourceDeleted) error { |
||||
return s.SQLStore.InTransaction(ctx, func(ctx context.Context) error { |
||||
if err := s.deleteCorrelationsBySourceUID(ctx, DeleteCorrelationsBySourceUIDCommand{ |
||||
SourceUID: event.UID, |
||||
}); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := s.deleteCorrelationsByTargetUID(ctx, DeleteCorrelationsByTargetUIDCommand{ |
||||
TargetUID: event.UID, |
||||
}); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
}) |
||||
} |
||||
@ -0,0 +1,70 @@ |
||||
package correlations |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
// createCorrelation adds a correlation
|
||||
func (s CorrelationsService) createCorrelation(ctx context.Context, cmd CreateCorrelationCommand) (Correlation, error) { |
||||
correlation := Correlation{ |
||||
UID: util.GenerateShortUID(), |
||||
SourceUID: cmd.SourceUID, |
||||
TargetUID: cmd.TargetUID, |
||||
Label: cmd.Label, |
||||
Description: cmd.Description, |
||||
} |
||||
|
||||
err := s.SQLStore.WithTransactionalDbSession(ctx, func(session *sqlstore.DBSession) error { |
||||
var err error |
||||
|
||||
query := &datasources.GetDataSourceQuery{ |
||||
OrgId: cmd.OrgId, |
||||
Uid: cmd.SourceUID, |
||||
} |
||||
if err = s.DataSourceService.GetDataSource(ctx, query); err != nil { |
||||
return ErrSourceDataSourceDoesNotExists |
||||
} |
||||
|
||||
if !cmd.SkipReadOnlyCheck && query.Result.ReadOnly { |
||||
return ErrSourceDataSourceReadOnly |
||||
} |
||||
|
||||
if err = s.DataSourceService.GetDataSource(ctx, &datasources.GetDataSourceQuery{ |
||||
OrgId: cmd.OrgId, |
||||
Uid: cmd.TargetUID, |
||||
}); err != nil { |
||||
return ErrTargetDataSourceDoesNotExists |
||||
} |
||||
|
||||
_, err = session.Insert(correlation) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
}) |
||||
|
||||
if err != nil { |
||||
return Correlation{}, err |
||||
} |
||||
|
||||
return correlation, nil |
||||
} |
||||
|
||||
func (s CorrelationsService) deleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error { |
||||
return s.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error { |
||||
_, err := session.Delete(&Correlation{SourceUID: cmd.SourceUID}) |
||||
return err |
||||
}) |
||||
} |
||||
|
||||
func (s CorrelationsService) deleteCorrelationsByTargetUID(ctx context.Context, cmd DeleteCorrelationsByTargetUIDCommand) error { |
||||
return s.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error { |
||||
_, err := session.Delete(&Correlation{TargetUID: cmd.TargetUID}) |
||||
return err |
||||
}) |
||||
} |
||||
@ -0,0 +1,66 @@ |
||||
package correlations |
||||
|
||||
import ( |
||||
"errors" |
||||
) |
||||
|
||||
var ( |
||||
ErrSourceDataSourceReadOnly = errors.New("source data source is read only") |
||||
ErrSourceDataSourceDoesNotExists = errors.New("source data source does not exist") |
||||
ErrTargetDataSourceDoesNotExists = errors.New("target data source does not exist") |
||||
ErrCorrelationFailedGenerateUniqueUid = errors.New("failed to generate unique correlation UID") |
||||
ErrCorrelationIdentifierNotSet = errors.New("source identifier and org id are needed to be able to edit correlations") |
||||
) |
||||
|
||||
// Correlation is the model for correlations definitions
|
||||
type Correlation struct { |
||||
// Unique identifier of the correlation
|
||||
// example: 50xhMlg9k
|
||||
UID string `json:"uid" xorm:"pk 'uid'"` |
||||
// UID of the data source the correlation originates from
|
||||
// example:d0oxYRg4z
|
||||
SourceUID string `json:"sourceUid" xorm:"pk 'source_uid'"` |
||||
// UID of the data source the correlation points to
|
||||
// example:PE1C5CBDA0504A6A3
|
||||
TargetUID string `json:"targetUid" xorm:"target_uid"` |
||||
// Label identifying the correlation
|
||||
// example: My Label
|
||||
Label string `json:"label" xorm:"label"` |
||||
// Description of the correlation
|
||||
// example: Logs to Traces
|
||||
Description string `json:"description" xorm:"description"` |
||||
} |
||||
|
||||
// CreateCorrelationResponse is the response struct for CreateCorrelationCommand
|
||||
// swagger:model
|
||||
type CreateCorrelationResponse struct { |
||||
Result Correlation `json:"result"` |
||||
// example: Correlation created
|
||||
Message string `json:"message"` |
||||
} |
||||
|
||||
// CreateCorrelationCommand is the command for creating a correlation
|
||||
// swagger:model
|
||||
type CreateCorrelationCommand struct { |
||||
// UID of the data source for which correlation is created.
|
||||
SourceUID string `json:"-"` |
||||
OrgId int64 `json:"-"` |
||||
SkipReadOnlyCheck bool `json:"-"` |
||||
// Target data source UID to which the correlation is created
|
||||
// example:PE1C5CBDA0504A6A3
|
||||
TargetUID string `json:"targetUid" binding:"Required"` |
||||
// Optional label identifying the correlation
|
||||
// example: My label
|
||||
Label string `json:"label"` |
||||
// Optional description of the correlation
|
||||
// example: Logs to Traces
|
||||
Description string `json:"description"` |
||||
} |
||||
|
||||
type DeleteCorrelationsBySourceUIDCommand struct { |
||||
SourceUID string |
||||
} |
||||
|
||||
type DeleteCorrelationsByTargetUIDCommand struct { |
||||
TargetUID string |
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
apiVersion: 1 |
||||
|
||||
datasources: |
||||
- name: Graphite |
||||
type: graphite |
||||
uid: graphite |
||||
access: proxy |
||||
url: http://localhost:8080 |
||||
correlations: |
||||
- targetUid: graphite |
||||
label: a label |
||||
description: a description |
||||
- targetUid: graphite |
||||
label: a second label |
||||
description: a second description |
||||
@ -0,0 +1,21 @@ |
||||
package migrations |
||||
|
||||
import ( |
||||
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator" |
||||
) |
||||
|
||||
func addCorrelationsMigrations(mg *Migrator) { |
||||
correlationsV1 := Table{ |
||||
Name: "correlation", |
||||
Columns: []*Column{ |
||||
{Name: "uid", Type: DB_NVarchar, Length: 40, Nullable: false, IsPrimaryKey: true}, |
||||
{Name: "source_uid", Type: DB_NVarchar, Length: 40, Nullable: false, IsPrimaryKey: true}, |
||||
// Nullable because in the future we want to have correlations to external resources
|
||||
{Name: "target_uid", Type: DB_NVarchar, Length: 40, Nullable: true}, |
||||
{Name: "label", Type: DB_Text, Nullable: false}, |
||||
{Name: "description", Type: DB_Text, Nullable: false}, |
||||
}, |
||||
} |
||||
|
||||
mg.AddMigration("create correlation table v1", NewAddTableMigration(correlationsV1)) |
||||
} |
||||
@ -0,0 +1,84 @@ |
||||
package correlations |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"fmt" |
||||
"net/http" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/server" |
||||
"github.com/grafana/grafana/pkg/services/datasources" |
||||
"github.com/grafana/grafana/pkg/services/user" |
||||
"github.com/grafana/grafana/pkg/tests/testinfra" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
type TestContext struct { |
||||
env server.TestEnv |
||||
t *testing.T |
||||
} |
||||
|
||||
func NewTestEnv(t *testing.T) TestContext { |
||||
t.Helper() |
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ |
||||
DisableAnonymous: true, |
||||
}) |
||||
_, env := testinfra.StartGrafanaEnv(t, dir, path) |
||||
|
||||
return TestContext{ |
||||
env: *env, |
||||
t: t, |
||||
} |
||||
} |
||||
|
||||
type User struct { |
||||
username string |
||||
password string |
||||
} |
||||
|
||||
type PostParams struct { |
||||
url string |
||||
body string |
||||
user User |
||||
} |
||||
|
||||
func (c TestContext) Post(params PostParams) *http.Response { |
||||
c.t.Helper() |
||||
buf := bytes.NewReader([]byte(params.body)) |
||||
baseUrl := fmt.Sprintf("http://%s", c.env.Server.HTTPServer.Listener.Addr()) |
||||
if params.user.username != "" && params.user.password != "" { |
||||
baseUrl = fmt.Sprintf("http://%s:%s@%s", params.user.username, params.user.password, c.env.Server.HTTPServer.Listener.Addr()) |
||||
} |
||||
|
||||
// nolint:gosec
|
||||
resp, err := http.Post( |
||||
fmt.Sprintf( |
||||
"%s%s", |
||||
baseUrl, |
||||
params.url, |
||||
), |
||||
"application/json", |
||||
buf, |
||||
) |
||||
require.NoError(c.t, err) |
||||
|
||||
return resp |
||||
} |
||||
|
||||
func (c TestContext) createUser(cmd user.CreateUserCommand) { |
||||
c.t.Helper() |
||||
|
||||
c.env.SQLStore.Cfg.AutoAssignOrg = true |
||||
c.env.SQLStore.Cfg.AutoAssignOrgId = 1 |
||||
|
||||
_, err := c.env.SQLStore.CreateUser(context.Background(), cmd) |
||||
require.NoError(c.t, err) |
||||
} |
||||
|
||||
func (c TestContext) createDs(cmd *datasources.AddDataSourceCommand) { |
||||
c.t.Helper() |
||||
|
||||
err := c.env.SQLStore.AddDataSource(context.Background(), cmd) |
||||
require.NoError(c.t, err) |
||||
} |
||||
@ -0,0 +1,248 @@ |
||||
package correlations |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/correlations" |
||||
"github.com/grafana/grafana/pkg/services/datasources" |
||||
"github.com/grafana/grafana/pkg/services/user" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
type errorResponseBody struct { |
||||
Message string `json:"message"` |
||||
Error string `json:"error"` |
||||
} |
||||
|
||||
func TestIntegrationCreateCorrelation(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
ctx := NewTestEnv(t) |
||||
|
||||
adminUser := User{ |
||||
username: "admin", |
||||
password: "admin", |
||||
} |
||||
editorUser := User{ |
||||
username: "editor", |
||||
password: "editor", |
||||
} |
||||
|
||||
ctx.createUser(user.CreateUserCommand{ |
||||
DefaultOrgRole: string(models.ROLE_EDITOR), |
||||
Password: editorUser.password, |
||||
Login: editorUser.username, |
||||
}) |
||||
ctx.createUser(user.CreateUserCommand{ |
||||
DefaultOrgRole: string(models.ROLE_ADMIN), |
||||
Password: adminUser.password, |
||||
Login: adminUser.username, |
||||
}) |
||||
|
||||
createDsCommand := &datasources.AddDataSourceCommand{ |
||||
Name: "read-only", |
||||
Type: "loki", |
||||
ReadOnly: true, |
||||
OrgId: 1, |
||||
} |
||||
ctx.createDs(createDsCommand) |
||||
readOnlyDS := createDsCommand.Result.Uid |
||||
|
||||
createDsCommand = &datasources.AddDataSourceCommand{ |
||||
Name: "writable", |
||||
Type: "loki", |
||||
OrgId: 1, |
||||
} |
||||
ctx.createDs(createDsCommand) |
||||
writableDs := createDsCommand.Result.Uid |
||||
|
||||
t.Run("Unauthenticated users shouldn't be able to create correlations", func(t *testing.T) { |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", "some-ds-uid"), |
||||
body: ``, |
||||
}) |
||||
require.Equal(t, http.StatusUnauthorized, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response errorResponseBody |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, "Unauthorized", response.Message) |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
|
||||
t.Run("non org admin shouldn't be able to create correlations", func(t *testing.T) { |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", "some-ds-uid"), |
||||
body: ``, |
||||
user: editorUser, |
||||
}) |
||||
require.Equal(t, http.StatusForbidden, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response errorResponseBody |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Contains(t, response.Message, "Permissions needed: datasources:write") |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
|
||||
t.Run("missing source data source in body should result in a 400", func(t *testing.T) { |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", "nonexistent-ds-uid"), |
||||
body: `{}`, |
||||
user: adminUser, |
||||
}) |
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response errorResponseBody |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, "bad request data", response.Message) |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
|
||||
t.Run("inexistent source data source should result in a 404", func(t *testing.T) { |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", "nonexistent-ds-uid"), |
||||
body: fmt.Sprintf(`{ |
||||
"targetUid": "%s" |
||||
}`, writableDs), |
||||
user: adminUser, |
||||
}) |
||||
require.Equal(t, http.StatusNotFound, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response errorResponseBody |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, "Data source not found", response.Message) |
||||
require.Equal(t, correlations.ErrSourceDataSourceDoesNotExists.Error(), response.Error) |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
|
||||
t.Run("inexistent target data source should result in a 404", func(t *testing.T) { |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", writableDs), |
||||
body: `{ |
||||
"targetUid": "nonexistent-uid-uid" |
||||
}`, |
||||
user: adminUser, |
||||
}) |
||||
require.Equal(t, http.StatusNotFound, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response errorResponseBody |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, "Data source not found", response.Message) |
||||
require.Equal(t, correlations.ErrTargetDataSourceDoesNotExists.Error(), response.Error) |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
|
||||
t.Run("creating a correlation originating from a read-only data source should result in a 403", func(t *testing.T) { |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", readOnlyDS), |
||||
body: fmt.Sprintf(`{ |
||||
"targetUid": "%s" |
||||
}`, readOnlyDS), |
||||
user: adminUser, |
||||
}) |
||||
require.Equal(t, http.StatusForbidden, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response errorResponseBody |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, "Data source is read only", response.Message) |
||||
require.Equal(t, correlations.ErrSourceDataSourceReadOnly.Error(), response.Error) |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
|
||||
t.Run("creating a correlation pointing to a read-only data source should work", func(t *testing.T) { |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", writableDs), |
||||
body: fmt.Sprintf(`{ |
||||
"targetUid": "%s" |
||||
}`, readOnlyDS), |
||||
user: adminUser, |
||||
}) |
||||
require.Equal(t, http.StatusOK, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response correlations.CreateCorrelationResponse |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, "Correlation created", response.Message) |
||||
require.Equal(t, writableDs, response.Result.SourceUID) |
||||
require.Equal(t, readOnlyDS, response.Result.TargetUID) |
||||
require.Equal(t, "", response.Result.Description) |
||||
require.Equal(t, "", response.Result.Label) |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
|
||||
t.Run("Should correctly create a correlation", func(t *testing.T) { |
||||
description := "a description" |
||||
label := "a label" |
||||
res := ctx.Post(PostParams{ |
||||
url: fmt.Sprintf("/api/datasources/uid/%s/correlations", writableDs), |
||||
body: fmt.Sprintf(`{ |
||||
"targetUid": "%s", |
||||
"description": "%s", |
||||
"label": "%s" |
||||
}`, writableDs, description, label), |
||||
user: adminUser, |
||||
}) |
||||
require.Equal(t, http.StatusOK, res.StatusCode) |
||||
|
||||
responseBody, err := ioutil.ReadAll(res.Body) |
||||
require.NoError(t, err) |
||||
|
||||
var response correlations.CreateCorrelationResponse |
||||
err = json.Unmarshal(responseBody, &response) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, "Correlation created", response.Message) |
||||
require.Equal(t, writableDs, response.Result.SourceUID) |
||||
require.Equal(t, writableDs, response.Result.TargetUID) |
||||
require.Equal(t, description, response.Result.Description) |
||||
require.Equal(t, label, response.Result.Label) |
||||
|
||||
require.NoError(t, res.Body.Close()) |
||||
}) |
||||
} |
||||
Loading…
Reference in new issue