influxdb/tests/client.go

705 lines
21 KiB
Go

package tests
import (
"bytes"
"context"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/influxdata/flux/csv"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/authorization"
influxhttp "github.com/influxdata/influxdb/v2/http"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/pkg/httpc"
"github.com/influxdata/influxdb/v2/tenant"
)
type ClientConfig struct {
UserID platform.ID
OrgID platform.ID
BucketID platform.ID
DocumentsNamespace string
// If Session is provided, Token is ignored.
Token string
Session *influxdb.Session
}
// Client provides an API for writing, querying, and interacting with
// resources like authorizations, buckets, and organizations.
type Client struct {
Client *httpc.Client
*influxhttp.Service
*authorization.AuthorizationClientService
*tenant.BucketClientService
*tenant.OrgClientService
*tenant.UserClientService
ClientConfig
}
// NewClient initialises a new Client which is ready to write points to the HTTP write endpoint.
func NewClient(endpoint string, config ClientConfig) (*Client, error) {
opts := make([]httpc.ClientOptFn, 0)
if config.Session != nil {
config.Token = ""
opts = append(opts, httpc.WithSessionCookie(config.Session.Key))
}
hc, err := influxhttp.NewHTTPClient(endpoint, config.Token, false, opts...)
if err != nil {
return nil, err
}
svc, err := influxhttp.NewService(hc, endpoint, config.Token)
if err != nil {
return nil, err
}
return &Client{
Client: hc,
Service: svc,
AuthorizationClientService: &authorization.AuthorizationClientService{Client: hc},
BucketClientService: &tenant.BucketClientService{Client: hc},
OrgClientService: &tenant.OrgClientService{Client: hc},
UserClientService: &tenant.UserClientService{Client: hc},
ClientConfig: config,
}, nil
}
// Open opens the client
func (c *Client) Open() error { return nil }
// Close closes the client
func (c *Client) Close() error { return nil }
// MustWriteBatch calls WriteBatch, panicking if an error is encountered.
func (c *Client) MustWriteBatch(points string) {
if err := c.WriteBatch(points); err != nil {
panic(err)
}
}
// WriteBatch writes the current batch of points to the HTTP endpoint.
func (c *Client) WriteBatch(points string) error {
return c.WriteService.WriteTo(
context.Background(),
influxdb.BucketFilter{
ID: &c.BucketID,
OrganizationID: &c.OrgID,
},
strings.NewReader(points),
)
}
// Query returns the CSV response from a flux query to the HTTP API.
//
// This also remove all the \r to make it easier to write tests.
func (c *Client) QueryFlux(org, query string) (string, error) {
var csv string
csvResp := func(resp *http.Response) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
// remove the \r to simplify testing against a body of CSV.
body = bytes.ReplaceAll(body, []byte("\r"), nil)
csv = string(body)
return nil
}
qr := QueryRequestBody(query)
err := c.Client.PostJSON(qr, fluxPath).
QueryParams([2]string{"org", org}).
Accept("text/csv").
RespFn(csvResp).
StatusFn(httpc.StatusIn(http.StatusOK)).
Do(context.Background())
return csv, err
}
const (
fluxPath = "/api/v2/query"
// This is the only namespace for documents present after init.
DefaultDocumentsNamespace = "templates"
)
// QueryRequestBody creates a body for a flux query using common CSV output params.
// Headers are included, but, annotations are not.
func QueryRequestBody(flux string) *influxhttp.QueryRequest {
header := true
return &influxhttp.QueryRequest{
Type: "flux",
Query: flux,
Dialect: influxhttp.QueryDialect{
Header: &header,
Delimiter: ",",
CommentPrefix: "#",
DateTimeFormat: "RFC3339",
Annotations: csv.DefaultEncoderConfig().Annotations,
},
}
}
// MustCreateAuth creates an auth or is a fatal error.
// Used in tests where the content of the bucket does not matter.
//
// This authorization token is an operator token for the default
// organization for the default user.
func (c *Client) MustCreateAuth(t *testing.T) platform.ID {
t.Helper()
perms := influxdb.OperPermissions()
auth := &influxdb.Authorization{
OrgID: c.OrgID,
UserID: c.UserID,
Permissions: perms,
}
err := c.CreateAuthorization(context.Background(), auth)
if err != nil {
t.Fatalf("unable to create auth: %v", err)
}
return auth.ID
}
// MustCreateBucket creates a bucket or is a fatal error.
// Used in tests where the content of the bucket does not matter.
func (c *Client) MustCreateBucket(t *testing.T) platform.ID {
t.Helper()
bucket := &influxdb.Bucket{OrgID: c.OrgID, Name: "n1"}
err := c.CreateBucket(context.Background(), bucket)
if err != nil {
t.Fatalf("unable to create bucket: %v", err)
}
return bucket.ID
}
// MustCreateOrg creates an org or is a fatal error.
// Used in tests where the content of the org does not matter.
func (c *Client) MustCreateOrg(t *testing.T) platform.ID {
t.Helper()
org := &influxdb.Organization{Name: "n1"}
err := c.CreateOrganization(context.Background(), org)
if err != nil {
t.Fatalf("unable to create org: %v", err)
}
return org.ID
}
// MustCreateLabel creates a label or is a fatal error.
// Used in tests where the content of the label does not matter.
func (c *Client) MustCreateLabel(t *testing.T) platform.ID {
t.Helper()
l := &influxdb.Label{OrgID: c.OrgID, Name: "n1"}
err := c.CreateLabel(context.Background(), l)
if err != nil {
t.Fatalf("unable to create label: %v", err)
}
return l.ID
}
// MustCreateCheck creates a check or is a fatal error.
// Used in tests where the content of the check does not matter.
func (c *Client) MustCreateCheck(t *testing.T) platform.ID {
t.Helper()
chk, err := c.CreateCheck(context.Background(), MockCheck("c", c.OrgID, c.UserID))
if err != nil {
t.Fatalf("unable to create check: %v", err)
}
return chk.ID
}
// MustCreateTelegraf creates a telegraf config or is a fatal error.
// Used in tests where the content of the telegraf config does not matter.
func (c *Client) MustCreateTelegraf(t *testing.T) platform.ID {
t.Helper()
tc := &influxdb.TelegrafConfig{
OrgID: c.OrgID,
Name: "n1",
Description: "d1",
Config: "[[howdy]]",
}
unused := platform.ID(1) /* this id is not used in the API */
err := c.CreateTelegrafConfig(context.Background(), tc, unused)
if err != nil {
t.Fatalf("unable to create telegraf config: %v", err)
}
return tc.ID
}
// MustCreateUser creates a user or is a fatal error.
// Used in tests where the content of the user does not matter.
func (c *Client) MustCreateUser(t *testing.T) platform.ID {
t.Helper()
u := &influxdb.User{Name: "n1"}
err := c.CreateUser(context.Background(), u)
if err != nil {
t.Fatalf("unable to create user: %v", err)
}
return u.ID
}
// MustCreateVariable creates a variable or is a fatal error.
// Used in tests where the content of the variable does not matter.
func (c *Client) MustCreateVariable(t *testing.T) platform.ID {
t.Helper()
v := &influxdb.Variable{
OrganizationID: c.OrgID,
Name: "n1",
Arguments: &influxdb.VariableArguments{
Type: "constant",
Values: influxdb.VariableConstantValues{"v1", "v2"},
},
}
err := c.CreateVariable(context.Background(), v)
if err != nil {
t.Fatalf("unable to create variable: %v", err)
}
return v.ID
}
// MustCreateNotificationEndpoint creates a notification endpoint or is a fatal error.
// Used in tests where the content of the notification endpoint does not matter.
func (c *Client) MustCreateNotificationEndpoint(t *testing.T) platform.ID {
t.Helper()
ne := ValidNotificationEndpoint(c.OrgID)
err := c.CreateNotificationEndpoint(context.Background(), ne, c.UserID)
if err != nil {
t.Fatalf("unable to create notification endpoint: %v", err)
}
return ne.GetID()
}
// MustCreateNotificationRule creates a Notification Rule or is a fatal error
// Used in tests where the content of the notification rule does not matter
func (c *Client) MustCreateNotificationRule(t *testing.T) platform.ID {
t.Helper()
ctx := context.Background()
ne := ValidCustomNotificationEndpoint(c.OrgID, time.Now().String())
err := c.CreateNotificationEndpoint(ctx, ne, c.UserID)
if err != nil {
t.Fatalf("unable to create notification endpoint: %v", err)
}
endpointID := ne.GetID()
r := ValidNotificationRule(c.OrgID, endpointID)
rc := influxdb.NotificationRuleCreate{NotificationRule: r, Status: influxdb.Active}
err = c.CreateNotificationRule(ctx, rc, c.UserID)
if err != nil {
t.Fatalf("unable to create notification rule: %v", err)
}
// we don't need this endpoint, so delete it to be compatible with other tests
_, _, err = c.DeleteNotificationEndpoint(ctx, endpointID)
if err != nil {
t.Fatalf("unable to delete notification endpoint: %v", err)
}
return r.GetID()
}
// MustCreateDBRPMapping creates a DBRP Mapping or is a fatal error.
// Used in tests where the content of the mapping does not matter.
// The created mapping points to the user's default bucket.
func (c *Client) MustCreateDBRPMapping(t *testing.T) platform.ID {
t.Helper()
ctx := context.Background()
m := &influxdb.DBRPMapping{
Database: "db",
RetentionPolicy: "rp",
OrganizationID: c.OrgID,
BucketID: c.BucketID,
}
if err := c.DBRPMappingService.Create(ctx, m); err != nil {
t.Fatalf("unable to create DBRP mapping: %v", err)
}
return m.ID
}
// MustCreateResource will create a generic resource via the API.
// Used in tests where the content of the resource does not matter.
//
// // Create one of each org resource
// for _, r := range influxdb.OrgResourceTypes {
// client.MustCreateResource(t, r)
// }
//
//
// // Create a variable:
// id := client.MustCreateResource(t, influxdb.VariablesResourceType)
// defer client.MustDeleteResource(t, influxdb.VariablesResourceType, id)
func (c *Client) MustCreateResource(t *testing.T, r influxdb.ResourceType) platform.ID {
t.Helper()
switch r {
case influxdb.AuthorizationsResourceType: // 0
return c.MustCreateAuth(t)
case influxdb.BucketsResourceType: // 1
return c.MustCreateBucket(t)
case influxdb.OrgsResourceType: // 3
return c.MustCreateOrg(t)
case influxdb.SourcesResourceType: // 4
t.Skip("I think sources are going to be removed right?")
case influxdb.TasksResourceType: // 5
t.Skip("Task go client is not yet created")
case influxdb.TelegrafsResourceType: // 6
return c.MustCreateTelegraf(t)
case influxdb.UsersResourceType: // 7
return c.MustCreateUser(t)
case influxdb.VariablesResourceType: // 8
return c.MustCreateVariable(t)
case influxdb.ScraperResourceType: // 9
t.Skip("Scraper go client is not yet created")
case influxdb.SecretsResourceType: // 10
t.Skip("Secrets go client is not yet created")
case influxdb.LabelsResourceType: // 11
return c.MustCreateLabel(t)
case influxdb.ViewsResourceType: // 12
t.Skip("Are views still a thing?")
case influxdb.NotificationRuleResourceType: // 14
return c.MustCreateNotificationRule(t)
case influxdb.NotificationEndpointResourceType: // 15
return c.MustCreateNotificationEndpoint(t)
case influxdb.ChecksResourceType: // 16
return c.MustCreateCheck(t)
case influxdb.DBRPResourceType: // 17
return c.MustCreateDBRPMapping(t)
}
return 0
}
// DeleteResource will remove a resource using the API.
func (c *Client) DeleteResource(t *testing.T, r influxdb.ResourceType, id platform.ID) error {
t.Helper()
ctx := context.Background()
switch r {
case influxdb.AuthorizationsResourceType: // 0
return c.DeleteAuthorization(ctx, id)
case influxdb.BucketsResourceType: // 1
return c.DeleteBucket(context.Background(), id)
case influxdb.OrgsResourceType: // 3
return c.DeleteOrganization(ctx, id)
case influxdb.SourcesResourceType: // 4
t.Skip("I think sources are going to be removed right?")
case influxdb.TasksResourceType: // 5
t.Skip("Task go client is not yet created")
case influxdb.TelegrafsResourceType: // 6
return c.DeleteTelegrafConfig(ctx, id)
case influxdb.UsersResourceType: // 7
return c.DeleteUser(ctx, id)
case influxdb.VariablesResourceType: // 8
return c.DeleteVariable(ctx, id)
case influxdb.ScraperResourceType: // 9
t.Skip("Scraper go client is not yet created")
case influxdb.SecretsResourceType: // 10
t.Skip("Secrets go client is not yet created")
case influxdb.LabelsResourceType: // 11
return c.DeleteLabel(ctx, id)
case influxdb.ViewsResourceType: // 12
t.Skip("Are views still a thing?")
case influxdb.NotificationRuleResourceType: // 14
return c.DeleteNotificationRule(ctx, id)
case influxdb.NotificationEndpointResourceType: // 15
// Ignore the other results as suggested by goDoc.
_, _, err := c.DeleteNotificationEndpoint(ctx, id)
return err
case influxdb.ChecksResourceType: // 16
return c.DeleteCheck(ctx, id)
case influxdb.DBRPResourceType: // 17
return c.DBRPMappingService.Delete(ctx, c.OrgID, id)
}
return nil
}
// MustDeleteResource requires no error when deleting a resource.
func (c *Client) MustDeleteResource(t *testing.T, r influxdb.ResourceType, id platform.ID) {
t.Helper()
if err := c.DeleteResource(t, r, id); err != nil {
t.Fatalf("unable to delete resource %v %v: %v", r, id, err)
}
}
// FindAll returns all the IDs of a specific resource type.
func (c *Client) FindAll(t *testing.T, r influxdb.ResourceType) ([]platform.ID, error) {
t.Helper()
var ids []platform.ID
ctx := context.Background()
switch r {
case influxdb.AuthorizationsResourceType: // 0
rs, _, err := c.FindAuthorizations(ctx, influxdb.AuthorizationFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.BucketsResourceType: // 1
rs, _, err := c.FindBuckets(ctx, influxdb.BucketFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.OrgsResourceType: // 3
rs, _, err := c.FindOrganizations(ctx, influxdb.OrganizationFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.SourcesResourceType: // 4
t.Skip("I think sources are going to be removed right?")
case influxdb.TasksResourceType: // 5
t.Skip("Task go client is not yet created")
case influxdb.TelegrafsResourceType: // 6
rs, _, err := c.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.UsersResourceType: // 7
rs, _, err := c.FindUsers(ctx, influxdb.UserFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.VariablesResourceType: // 8
rs, err := c.FindVariables(ctx, influxdb.VariableFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.ScraperResourceType: // 9
t.Skip("Scraper go client is not yet created")
case influxdb.SecretsResourceType: // 10
t.Skip("Secrets go client is not yet created")
case influxdb.LabelsResourceType: // 11
rs, err := c.FindLabels(ctx, influxdb.LabelFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.ViewsResourceType: // 12
t.Skip("Are views still a thing?")
case influxdb.NotificationRuleResourceType: // 14
rs, _, err := c.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{OrgID: &c.OrgID})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.GetID())
}
return ids, nil
case influxdb.NotificationEndpointResourceType: // 15
rs, _, err := c.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{OrgID: &c.OrgID})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.GetID())
}
case influxdb.ChecksResourceType: // 16
rs, _, err := c.FindChecks(ctx, influxdb.CheckFilter{})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
case influxdb.DBRPResourceType: // 17
rs, _, err := c.DBRPMappingService.FindMany(ctx, influxdb.DBRPMappingFilter{OrgID: &c.OrgID})
if err != nil {
return nil, err
}
for _, r := range rs {
ids = append(ids, r.ID)
}
}
return ids, nil
}
// MustFindAll returns all the IDs of a specific resource type; any error
// is fatal.
func (c *Client) MustFindAll(t *testing.T, r influxdb.ResourceType) []platform.ID {
t.Helper()
ids, err := c.FindAll(t, r)
if err != nil {
t.Fatalf("unexpected error finding resources %v: %v", r, err)
}
return ids
}
func (c *Client) AddURM(u platform.ID, typ influxdb.UserType, r influxdb.ResourceType, id platform.ID) error {
access := &influxdb.UserResourceMapping{
UserID: u,
UserType: typ,
MappingType: influxdb.UserMappingType,
ResourceType: r,
ResourceID: id,
}
return c.CreateUserResourceMapping(
context.Background(),
access,
)
}
// AddOwner associates the user as owner of the resource.
func (c *Client) AddOwner(user platform.ID, r influxdb.ResourceType, id platform.ID) error {
return c.AddURM(user, influxdb.Owner, r, id)
}
// MustAddOwner requires that the user is associated with the resource
// or the test will be stopped fatally.
func (c *Client) MustAddOwner(t *testing.T, user platform.ID, r influxdb.ResourceType, id platform.ID) {
t.Helper()
if err := c.AddOwner(user, r, id); err != nil {
t.Fatalf("unexpected error adding owner %v to %v: %v", user, id, err)
}
}
// AddMember associates the user as member of the resource.
func (c *Client) AddMember(user platform.ID, r influxdb.ResourceType, id platform.ID) error {
return c.AddURM(user, influxdb.Member, r, id)
}
// MustAddMember requires that the user is associated with the resource
// or the test will be stopped fatally.
func (c *Client) MustAddMember(t *testing.T, user platform.ID, r influxdb.ResourceType, id platform.ID) {
t.Helper()
if err := c.AddMember(user, r, id); err != nil {
t.Fatalf("unexpected error adding member %v to %v", user, id)
}
}
// RemoveURM removes association of the user to the resource.
// Interestingly the URM service does not make difference on the user type.
// I.e. removing an URM from a user to a resource, will delete every URM of every type
// from that user to that resource.
// Or, put in another way, there can only be one resource mapping from a user to a
// resource at a time: either you are a member, or an owner (in that case you are a member too).
func (c *Client) RemoveURM(user, id platform.ID) error {
return c.DeleteUserResourceMapping(context.Background(), id, user)
}
// RemoveSpecificURM gets around a client issue where deletes doesn't have enough context to remove a urm from
// a specific resource type
func (c *Client) RemoveSpecificURM(rt influxdb.ResourceType, ut influxdb.UserType, user, id platform.ID) error {
return c.SpecificURMSvc(rt, ut).DeleteUserResourceMapping(context.Background(), id, user)
}
// MustRemoveURM requires that the user is removed as owner/member from the resource.
func (c *Client) MustRemoveURM(t *testing.T, user, id platform.ID) {
t.Helper()
if err := c.RemoveURM(user, id); err != nil {
t.Fatalf("unexpected error removing org/resource mapping: %v", err)
}
}
// CreateLabelMapping creates a label mapping for label `l` to the resource with `id`.
func (c *Client) CreateLabelMapping(l platform.ID, r influxdb.ResourceType, id platform.ID) error {
mapping := &influxdb.LabelMapping{
LabelID: l,
ResourceType: r,
ResourceID: id,
}
return c.LabelService.CreateLabelMapping(
context.Background(),
mapping,
)
}
// MustCreateLabelMapping requires that the label is associated with the resource
// or the test will be stopped fatally.
func (c *Client) MustCreateLabelMapping(t *testing.T, l platform.ID, r influxdb.ResourceType, id platform.ID) {
t.Helper()
if err := c.CreateLabelMapping(l, r, id); err != nil {
t.Fatalf("unexpected error attaching label %v to %v: %v", l, id, err)
}
}
// FindLabelMappings finds the labels for the specified resource.
func (c *Client) FindLabelMappings(r influxdb.ResourceType, id platform.ID) ([]platform.ID, error) {
filter := influxdb.LabelMappingFilter{
ResourceType: r,
ResourceID: id,
}
ls, err := c.LabelService.FindResourceLabels(
context.Background(),
filter,
)
if err != nil {
return nil, err
}
var ids []platform.ID
for _, r := range ls {
ids = append(ids, r.ID)
}
return ids, nil
}
// MustFindLabelMappings makes the test fail if an error is found.
func (c *Client) MustFindLabelMappings(t *testing.T, r influxdb.ResourceType, id platform.ID) []platform.ID {
t.Helper()
ls, err := c.FindLabelMappings(r, id)
if err != nil {
t.Fatalf("unexpected error finding label mappings: %v", err)
}
return ls
}
// DeleteLabelMapping deletes the label for the specified resource.
func (c *Client) DeleteLabelMapping(l platform.ID, r influxdb.ResourceType, id platform.ID) error {
m := &influxdb.LabelMapping{
ResourceType: r,
ResourceID: id,
LabelID: l,
}
return c.LabelService.DeleteLabelMapping(
context.Background(),
m,
)
}
// MustDeleteLabelMapping makes the test fail if an error is found.
func (c *Client) MustDeleteLabelMapping(t *testing.T, l platform.ID, r influxdb.ResourceType, id platform.ID) {
t.Helper()
if err := c.DeleteLabelMapping(l, r, id); err != nil {
t.Fatalf("unexpected error deleting label %v from %v", l, id)
}
}