diff --git a/authorizer/task_test.go b/authorizer/task_test.go index 7503b3358b..bffe64b388 100644 --- a/authorizer/task_test.go +++ b/authorizer/task_test.go @@ -6,23 +6,26 @@ import ( "time" "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" "github.com/influxdata/influxdb/v2/authorizer" pctx "github.com/influxdata/influxdb/v2/context" + _ "github.com/influxdata/influxdb/v2/fluxinit/static" "github.com/influxdata/influxdb/v2/http" "github.com/influxdata/influxdb/v2/inmem" "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration/all" "github.com/influxdata/influxdb/v2/mock" - _ "github.com/influxdata/influxdb/v2/fluxinit/static" + "github.com/influxdata/influxdb/v2/tenant" "github.com/pkg/errors" "go.uber.org/zap/zaptest" ) func TestOnboardingValidation(t *testing.T) { - svc := newKVSVC(t) + _, onboard := setup(t) + ts := authorizer.NewTaskService(zaptest.NewLogger(t), mockTaskService(3, 2, 1)) - r, err := svc.OnboardInitialUser(context.Background(), &influxdb.OnboardingRequest{ + r, err := onboard.OnboardInitialUser(context.Background(), &influxdb.OnboardingRequest{ User: "Setec Astronomy", Password: "too many secrets", Org: "thing", @@ -120,9 +123,9 @@ func TestValidations(t *testing.T) { otherOrg = &influxdb.Organization{Name: "other_org"} ) - svc := newKVSVC(t) + svc, onboard := setup(t) - r, err := svc.OnboardInitialUser(context.Background(), &influxdb.OnboardingRequest{ + r, err := onboard.OnboardInitialUser(context.Background(), &influxdb.OnboardingRequest{ User: "Setec Astronomy", Password: "too many secrets", Org: "thing", @@ -572,7 +575,26 @@ from(bucket:"holder") |> range(start:-5m) |> to(bucket:"holder", org:"thing")` } } -func newKVSVC(t *testing.T) *kv.Service { +func setup(t *testing.T) (*tenant.Service, influxdb.OnboardingService) { + t.Helper() + + store := newStore(t) + + svc := tenant.NewService(tenant.NewStore(store)) + + authStore, err := authorization.NewStore(store) + if err != nil { + t.Fatal(err) + } + + authSvc := authorization.NewService(authStore, svc) + + onboard := tenant.NewOnboardService(svc, authSvc) + + return svc, onboard +} + +func newStore(t *testing.T) kv.Store { t.Helper() store := inmem.NewKVStore() @@ -581,5 +603,5 @@ func newKVSVC(t *testing.T) *kv.Service { t.Fatal(err) } - return kv.NewService(zaptest.NewLogger(t), store) + return store } diff --git a/checks/service_external_test.go b/checks/service_external_test.go index c8deca46b1..e7c82c3bb9 100644 --- a/checks/service_external_test.go +++ b/checks/service_external_test.go @@ -174,13 +174,12 @@ var taskCmpOptions = cmp.Options{ // CheckFields will include the IDGenerator, and checks type CheckFields struct { - IDGenerator influxdb.IDGenerator - TimeGenerator influxdb.TimeGenerator - TaskService influxdb.TaskService - Checks []influxdb.Check - Organizations []*influxdb.Organization - UserResourceMappings []*influxdb.UserResourceMapping - Tasks []influxdb.TaskCreate + IDGenerator influxdb.IDGenerator + TimeGenerator influxdb.TimeGenerator + TaskService influxdb.TaskService + Checks []influxdb.Check + Organizations []*influxdb.Organization + Tasks []influxdb.TaskCreate } type checkServiceFactory func(CheckFields, *testing.T) (influxdb.CheckService, influxdb.TaskService, string, func()) @@ -268,26 +267,6 @@ func CreateCheck( ID: MustIDBase16(orgOneID), }, }, - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(orgOneID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(fiveID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(orgOneID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(orgOneID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(twoID), - UserType: influxdb.Member, - }, - }, }, args: args{ userID: MustIDBase16(twoID), @@ -394,26 +373,6 @@ func CreateCheck( return MustIDBase16(checkTwoID) }, }, - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(orgTwoID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(orgOneID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(checkOneID), - ResourceType: influxdb.ChecksResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2006, 5, 4, 1, 2, 3, 0, time.UTC)}, Checks: []influxdb.Check{ deadman1, @@ -493,26 +452,6 @@ func CreateCheck( { name: "names should be unique within an organization", fields: CheckFields{ - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(orgTwoID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(orgOneID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(checkOneID), - ResourceType: influxdb.ChecksResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - }, IDGenerator: &mock.IDGenerator{ IDFn: func() influxdb.ID { return MustIDBase16(checkTwoID) @@ -585,26 +524,6 @@ func CreateCheck( }, }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2006, 5, 4, 1, 2, 3, 0, time.UTC)}, - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(orgTwoID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(twoID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(orgOneID), - ResourceType: influxdb.OrgsResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - { - ResourceID: MustIDBase16(checkOneID), - ResourceType: influxdb.ChecksResourceType, - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - }, - }, Organizations: []*influxdb.Organization{ { Name: "theorg", @@ -901,20 +820,6 @@ func FindChecks( { name: "find all checks", fields: CheckFields{ - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Organizations: []*influxdb.Organization{ { Name: "theorg", @@ -943,20 +848,6 @@ func FindChecks( { name: "find all checks by offset and limit", fields: CheckFields{ - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Organizations: []*influxdb.Organization{ { Name: "theorg", @@ -983,20 +874,6 @@ func FindChecks( { name: "find all checks by descending", fields: CheckFields{ - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Organizations: []*influxdb.Organization{ { Name: "theorg", @@ -1024,20 +901,6 @@ func FindChecks( { name: "find checks by organization name", fields: CheckFields{ - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Organizations: []*influxdb.Organization{ { Name: "theorg", @@ -1066,20 +929,6 @@ func FindChecks( { name: "find checks by organization id", fields: CheckFields{ - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Organizations: []*influxdb.Organization{ { Name: "theorg", @@ -1114,20 +963,6 @@ func FindChecks( ID: MustIDBase16(orgOneID), }, }, - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Checks: []influxdb.Check{ deadman1, threshold1, @@ -1168,12 +1003,7 @@ func FindChecks( defer done() ctx := context.Background() - filter := influxdb.CheckFilter{ - UserResourceMappingFilter: influxdb.UserResourceMappingFilter{ - UserID: tt.args.userID, - ResourceType: influxdb.ChecksResourceType, - }, - } + filter := influxdb.CheckFilter{} if tt.args.ID.Valid() { filter.ID = &tt.args.ID } @@ -1227,20 +1057,6 @@ func DeleteCheck( ID: MustIDBase16(orgOneID), }, }, - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Tasks: []influxdb.TaskCreate{ { Flux: `option task = { every: 10s, name: "foo" } @@ -1274,20 +1090,6 @@ data = from(bucket: "telegraf") |> range(start: -1m)`, ID: MustIDBase16(orgOneID), }, }, - UserResourceMappings: []*influxdb.UserResourceMapping{ - { - ResourceID: MustIDBase16(oneID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Owner, - ResourceType: influxdb.ChecksResourceType, - }, - { - ResourceID: MustIDBase16(twoID), - UserID: MustIDBase16(sixID), - UserType: influxdb.Member, - ResourceType: influxdb.ChecksResourceType, - }, - }, Tasks: []influxdb.TaskCreate{ { Flux: `option task = { every: 10s, name: "foo" } @@ -1327,12 +1129,7 @@ data = from(bucket: "telegraf") |> range(start: -1m)`, err := s.DeleteCheck(ctx, MustIDBase16(tt.args.ID)) influxErrsEqual(t, tt.wants.err, err) - filter := influxdb.CheckFilter{ - UserResourceMappingFilter: influxdb.UserResourceMappingFilter{ - UserID: tt.args.userID, - ResourceType: influxdb.ChecksResourceType, - }, - } + filter := influxdb.CheckFilter{} checks, _, err := s.FindChecks(ctx, filter) if err != nil { t.Fatalf("failed to retrieve checks: %v", err) diff --git a/checks/service_test.go b/checks/service_test.go index cd74e1e125..26ade32fb5 100644 --- a/checks/service_test.go +++ b/checks/service_test.go @@ -5,11 +5,13 @@ import ( "testing" "github.com/influxdata/influxdb/v2" + _ "github.com/influxdata/influxdb/v2/fluxinit/static" "github.com/influxdata/influxdb/v2/inmem" "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration/all" - _ "github.com/influxdata/influxdb/v2/fluxinit/static" + "github.com/influxdata/influxdb/v2/mock" "github.com/influxdata/influxdb/v2/query/fluxlang" + "github.com/influxdata/influxdb/v2/tenant" "go.uber.org/zap/zaptest" ) @@ -32,7 +34,11 @@ func TestCheckService(t *testing.T) { func initCheckService(f CheckFields, t *testing.T) (influxdb.CheckService, influxdb.TaskService, string, func()) { store, closeKVStore := NewKVTestStore(t) logger := zaptest.NewLogger(t) - svc := kv.NewService(logger, store, kv.ServiceConfig{ + + tenantStore := tenant.NewStore(store) + tenantSvc := tenant.NewService(tenantStore) + + svc := kv.NewService(logger, store, tenantSvc, kv.ServiceConfig{ FluxLanguageService: fluxlang.DefaultService, }) svc.IDGenerator = f.IDGenerator @@ -41,22 +47,19 @@ func initCheckService(f CheckFields, t *testing.T) (influxdb.CheckService, influ svc.TimeGenerator = influxdb.RealTimeGenerator{} } - checkService := NewService(logger, store, svc, svc) + checkService := NewService(logger, store, tenantSvc, svc) checkService.idGenerator = f.IDGenerator if f.TimeGenerator != nil { checkService.timeGenerator = f.TimeGenerator } ctx := context.Background() - for _, m := range f.UserResourceMappings { - if err := svc.CreateUserResourceMapping(ctx, m); err != nil { - t.Fatalf("failed to populate user resource mapping: %v", err) - } - } for _, o := range f.Organizations { - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate organizations") - } + mock.SetIDForFunc(&tenantStore.OrgIDGen, o.ID, func() { + if err := tenantSvc.CreateOrganization(ctx, o); err != nil { + t.Fatalf("failed to populate organizations") + } + }) } for _, c := range f.Checks { if err := checkService.PutCheck(ctx, c); err != nil { @@ -70,15 +73,10 @@ func initCheckService(f CheckFields, t *testing.T) (influxdb.CheckService, influ } return checkService, svc, kv.OpPrefix, func() { for _, o := range f.Organizations { - if err := svc.DeleteOrganization(ctx, o.ID); err != nil { + if err := tenantSvc.DeleteOrganization(ctx, o.ID); err != nil { t.Logf("failed to remove organization: %v", err) } } - for _, urm := range f.UserResourceMappings { - if err := svc.DeleteUserResourceMapping(ctx, urm.ResourceID, urm.UserID); err != nil && influxdb.ErrorCode(err) != influxdb.ENotFound { - t.Logf("failed to remove urm rule: %v", err) - } - } for _, c := range f.Checks { if err := checkService.DeleteCheck(ctx, c.GetID()); err != nil { t.Logf("failed to remove check: %v", err) diff --git a/cmd/influx/backup.go b/cmd/influx/backup.go index 23b6d948b5..34103d4a63 100644 --- a/cmd/influx/backup.go +++ b/cmd/influx/backup.go @@ -15,6 +15,7 @@ import ( "github.com/influxdata/influxdb/v2/http" "github.com/influxdata/influxdb/v2/kv" influxlogger "github.com/influxdata/influxdb/v2/logger" + "github.com/influxdata/influxdb/v2/tenant" "github.com/influxdata/influxdb/v2/v1/services/meta" "github.com/spf13/cobra" "go.uber.org/zap" @@ -39,6 +40,7 @@ type cmdBackupBuilder struct { backupService *http.BackupService kvStore *bolt.KVStore kvService *kv.Service + tenantService *tenant.Service metaClient *meta.Client logger *zap.Logger @@ -129,7 +131,11 @@ func (b *cmdBackupBuilder) backupRunE(cmd *cobra.Command, args []string) (err er // Open meta store so we can iterate over meta data. b.kvStore = bolt.NewKVStore(b.logger, filepath.Join(b.path, b.kvPath())) b.kvStore.WithDB(boltClient.DB()) - b.kvService = kv.NewService(b.logger, b.kvStore, kv.ServiceConfig{}) + + tenantStore := tenant.NewStore(b.kvStore) + b.tenantService = tenant.NewService(tenantStore) + + b.kvService = kv.NewService(b.logger, b.kvStore, b.tenantService, kv.ServiceConfig{}) b.metaClient = meta.NewClient(meta.NewConfig(), b.kvStore) if err := b.metaClient.Open(); err != nil { @@ -196,7 +202,7 @@ func (b *cmdBackupBuilder) backupOrganizations(ctx context.Context) (err error) } // Retrieve a list of all matching organizations. - orgs, _, err := b.kvService.FindOrganizations(ctx, filter) + orgs, _, err := b.tenantService.FindOrganizations(ctx, filter) if err != nil { return err } @@ -224,7 +230,7 @@ func (b *cmdBackupBuilder) backupBuckets(ctx context.Context, org *influxdb.Orga } // Retrieve a list of all matching organizations. - buckets, _, err := b.kvService.FindBuckets(ctx, filter) + buckets, _, err := b.tenantService.FindBuckets(ctx, filter) if err != nil { return err } diff --git a/cmd/influx/bucket.go b/cmd/influx/bucket.go index f35de3edaf..b5cdd8b47c 100644 --- a/cmd/influx/bucket.go +++ b/cmd/influx/bucket.go @@ -6,7 +6,7 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/cmd/internal" - "github.com/influxdata/influxdb/v2/http" + "github.com/influxdata/influxdb/v2/tenant" "github.com/spf13/cobra" ) @@ -352,7 +352,7 @@ func newBucketSVCs() (influxdb.BucketService, influxdb.OrganizationService, erro return nil, nil, err } - orgSvc := &http.OrganizationService{Client: httpClient} + orgSvc := &tenant.OrgClientService{Client: httpClient} - return &http.BucketService{Client: httpClient}, orgSvc, nil + return &tenant.BucketClientService{Client: httpClient}, orgSvc, nil } diff --git a/cmd/influx/main.go b/cmd/influx/main.go index e9c628faae..e5587ec5a8 100644 --- a/cmd/influx/main.go +++ b/cmd/influx/main.go @@ -20,6 +20,7 @@ import ( "github.com/influxdata/influxdb/v2/internal/fs" "github.com/influxdata/influxdb/v2/kit/cli" "github.com/influxdata/influxdb/v2/pkg/httpc" + "github.com/influxdata/influxdb/v2/tenant" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -441,11 +442,13 @@ func writeConfigToPath(tok, org, path, dir string) error { } func checkSetup(host string, skipVerify bool) error { - s := &http.SetupService{ - Addr: host, - InsecureSkipVerify: skipVerify, + httpClient, err := newHTTPClient() + if err != nil { + return err } + s := &tenant.OnboardClientService{Client: httpClient} + isOnboarding, err := s.IsOnboarding(context.Background()) if err != nil { return err diff --git a/cmd/influx/organization.go b/cmd/influx/organization.go index 90b78108e2..4d2927b36c 100644 --- a/cmd/influx/organization.go +++ b/cmd/influx/organization.go @@ -6,7 +6,7 @@ import ( "io" "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/http" + "github.com/influxdata/influxdb/v2/tenant" "github.com/spf13/cobra" ) @@ -525,9 +525,9 @@ func newOrgServices() (influxdb.OrganizationService, influxdb.UserResourceMappin return nil, nil, nil, err } - orgSVC := &http.OrganizationService{Client: client} - urmSVC := &http.UserResourceMappingService{Client: client} - userSVC := &http.UserService{Client: client} + orgSVC := &tenant.OrgClientService{Client: client} + urmSVC := &tenant.UserResourceMappingClient{Client: client} + userSVC := &tenant.UserClientService{Client: client} return orgSVC, urmSVC, userSVC, nil } @@ -538,7 +538,7 @@ func newOrganizationService() (influxdb.OrganizationService, error) { return nil, err } - return &http.OrganizationService{ + return &tenant.OrgClientService{ Client: client, }, nil } diff --git a/cmd/influx/restore.go b/cmd/influx/restore.go index 96dc7509d1..ec5c5d28d1 100644 --- a/cmd/influx/restore.go +++ b/cmd/influx/restore.go @@ -14,8 +14,8 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/bolt" "github.com/influxdata/influxdb/v2/http" - "github.com/influxdata/influxdb/v2/kv" influxlogger "github.com/influxdata/influxdb/v2/logger" + "github.com/influxdata/influxdb/v2/tenant" "github.com/influxdata/influxdb/v2/v1/services/meta" "github.com/spf13/cobra" "go.uber.org/zap" @@ -40,10 +40,10 @@ type cmdRestoreBuilder struct { kvEntry *influxdb.ManifestKVEntry shardEntries map[uint64]*influxdb.ManifestEntry - orgService *http.OrganizationService - bucketService *http.BucketService + orgService *tenant.OrgClientService + bucketService *tenant.BucketClientService restoreService *http.RestoreService - kvService *kv.Service + tenantService *tenant.Service metaClient *meta.Client logger *zap.Logger @@ -123,8 +123,8 @@ func (b *cmdRestoreBuilder) restoreRunE(cmd *cobra.Command, args []string) (err return err } - b.orgService = &http.OrganizationService{Client: client} - b.bucketService = &http.BucketService{Client: client} + b.orgService = &tenant.OrgClientService{Client: client} + b.bucketService = &tenant.BucketClientService{Client: client} if !b.full { return b.restorePartial(ctx) @@ -177,7 +177,9 @@ func (b *cmdRestoreBuilder) restorePartial(ctx context.Context) (err error) { // Open meta store so we can iterate over meta data. kvStore := bolt.NewKVStore(b.logger, boltClient.Path) kvStore.WithDB(boltClient.DB()) - b.kvService = kv.NewService(b.logger, kvStore, kv.ServiceConfig{}) + + tenantStore := tenant.NewStore(kvStore) + b.tenantService = tenant.NewService(tenantStore) b.metaClient = meta.NewClient(meta.NewConfig(), kvStore) if err := b.metaClient.Open(); err != nil { @@ -206,7 +208,7 @@ func (b *cmdRestoreBuilder) restoreOrganizations(ctx context.Context) (err error } // Retrieve a list of all matching organizations. - orgs, _, err := b.kvService.FindOrganizations(ctx, filter) + orgs, _, err := b.tenantService.FindOrganizations(ctx, filter) if err != nil { return err } @@ -251,7 +253,7 @@ func (b *cmdRestoreBuilder) restoreOrganization(ctx context.Context, org *influx } // Retrieve a list of all buckets for the organization in the local backup. - buckets, _, err := b.kvService.FindBuckets(ctx, filter) + buckets, _, err := b.tenantService.FindBuckets(ctx, filter) if err != nil { return err } diff --git a/cmd/influx/secret.go b/cmd/influx/secret.go index 954f62c8f0..0d797f3c46 100644 --- a/cmd/influx/secret.go +++ b/cmd/influx/secret.go @@ -6,7 +6,8 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/cmd/internal" - "github.com/influxdata/influxdb/v2/http" + isecret "github.com/influxdata/influxdb/v2/secret" + "github.com/influxdata/influxdb/v2/tenant" "github.com/spf13/cobra" "github.com/tcnksm/go-input" ) @@ -244,7 +245,8 @@ func newSecretSVCs() (influxdb.SecretService, influxdb.OrganizationService, func if err != nil { return nil, nil, nil, err } - orgSvc := &http.OrganizationService{Client: httpClient} - return &http.SecretService{Client: httpClient}, orgSvc, internal.GetSecret, nil + orgSvc := &tenant.OrgClientService{Client: httpClient} + + return &isecret.Client{Client: httpClient}, orgSvc, internal.GetSecret, nil } diff --git a/cmd/influx/template.go b/cmd/influx/template.go index 843649f76b..24a1b4503b 100644 --- a/cmd/influx/template.go +++ b/cmd/influx/template.go @@ -21,9 +21,9 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/cmd/influx/internal" internal2 "github.com/influxdata/influxdb/v2/cmd/internal" - ihttp "github.com/influxdata/influxdb/v2/http" ierror "github.com/influxdata/influxdb/v2/kit/errors" "github.com/influxdata/influxdb/v2/pkger" + "github.com/influxdata/influxdb/v2/tenant" "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" input "github.com/tcnksm/go-input" @@ -1235,7 +1235,7 @@ func newPkgerSVC() (pkger.SVC, influxdb.OrganizationService, error) { return nil, nil, err } - orgSvc := &ihttp.OrganizationService{ + orgSvc := &tenant.OrgClientService{ Client: httpClient, } diff --git a/cmd/influx/user.go b/cmd/influx/user.go index 01a7f3e116..c5f6874e49 100644 --- a/cmd/influx/user.go +++ b/cmd/influx/user.go @@ -8,6 +8,7 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/cmd/internal" "github.com/influxdata/influxdb/v2/http" + "github.com/influxdata/influxdb/v2/tenant" "github.com/spf13/cobra" "github.com/tcnksm/go-input" ) @@ -362,10 +363,10 @@ func newUserSVC() (cmdUserDeps, error) { if err != nil { return cmdUserDeps{}, err } - userSvc := &http.UserService{Client: httpClient} - orgSvc := &http.OrganizationService{Client: httpClient} - passSvc := &http.PasswordService{Client: httpClient} - urmSvc := &http.UserResourceMappingService{Client: httpClient} + userSvc := &tenant.UserClientService{Client: httpClient} + orgSvc := &tenant.OrgClientService{Client: httpClient} + passSvc := &tenant.PasswordClientService{Client: httpClient} + urmSvc := &tenant.UserResourceMappingClient{Client: httpClient} getPassFn := internal.GetPassword return cmdUserDeps{ diff --git a/cmd/influxd/launcher/launcher.go b/cmd/influxd/launcher/launcher.go index 3af15701bd..493454c501 100644 --- a/cmd/influxd/launcher/launcher.go +++ b/cmd/influxd/launcher/launcher.go @@ -735,28 +735,23 @@ func (m *Launcher) run(ctx context.Context) (err error) { return err } - serviceConfig := kv.ServiceConfig{ - SessionLength: time.Duration(m.sessionLength) * time.Minute, - FluxLanguageService: fluxlang.DefaultService, - } - - flushers := flushers{} + var flushers flushers switch m.storeType { case BoltStore: store := bolt.NewKVStore(m.log.With(zap.String("service", "kvstore-bolt")), m.boltPath) store.WithDB(m.boltClient.DB()) m.kvStore = store - m.kvService = kv.NewService(m.log.With(zap.String("store", "kv")), store, serviceConfig) if m.testing { flushers = append(flushers, store) } + case MemoryStore: store := inmem.NewKVStore() m.kvStore = store - m.kvService = kv.NewService(m.log.With(zap.String("store", "kv")), store, serviceConfig) if m.testing { flushers = append(flushers, store) } + default: err := fmt.Errorf("unknown store type %s; expected bolt or memory", m.storeType) m.log.Error("Failed opening bolt", zap.Error(err)) @@ -786,18 +781,27 @@ func (m *Launcher) run(ctx context.Context) (err error) { ) m.reg.MustRegister(m.boltClient) - var ( - variableSvc platform.VariableService = m.kvService - sourceSvc platform.SourceService = m.kvService - userLogSvc platform.UserOperationLogService = m.kvService - bucketLogSvc platform.BucketOperationLogService = m.kvService - orgLogSvc platform.OrganizationOperationLogService = m.kvService - scraperTargetSvc platform.ScraperTargetStoreService = m.kvService - ) - tenantStore := tenant.NewStore(m.kvStore) ts := tenant.NewSystem(tenantStore, m.log.With(zap.String("store", "new")), m.reg, metric.WithSuffix("new")) + serviceConfig := kv.ServiceConfig{ + FluxLanguageService: fluxlang.DefaultService, + } + + m.kvService = kv.NewService(m.log.With(zap.String("store", "kv")), m.kvStore, ts, serviceConfig) + + var ( + opLogSvc = tenant.NewOpLogService(m.kvStore, m.kvService) + userLogSvc platform.UserOperationLogService = opLogSvc + bucketLogSvc platform.BucketOperationLogService = opLogSvc + orgLogSvc platform.OrganizationOperationLogService = opLogSvc + ) + var ( + variableSvc platform.VariableService = m.kvService + sourceSvc platform.SourceService = m.kvService + scraperTargetSvc platform.ScraperTargetStoreService = m.kvService + ) + var authSvc platform.AuthorizationService { authStore, err := authorization.NewStore(m.kvStore) @@ -914,7 +918,14 @@ func (m *Launcher) run(ctx context.Context) (err error) { var taskSvc platform.TaskService { // create the task stack - combinedTaskService := taskbackend.NewAnalyticalStorage(m.log.With(zap.String("service", "task-analytical-store")), m.kvService, m.kvService, m.kvService, pointsWriter, query.QueryServiceBridge{AsyncQueryService: m.queryController}) + combinedTaskService := taskbackend.NewAnalyticalStorage( + m.log.With(zap.String("service", "task-analytical-store")), + m.kvService, + ts.BucketService, + m.kvService, + pointsWriter, + query.QueryServiceBridge{AsyncQueryService: m.queryController}, + ) executor, executorMetrics := executor.NewExecutor( m.log.With(zap.String("service", "task-executor")), @@ -1007,7 +1018,7 @@ func (m *Launcher) run(ctx context.Context) (err error) { var checkSvc platform.CheckService { coordinator := coordinator.NewCoordinator(m.log, m.scheduler, m.executor) - checkSvc = checks.NewService(m.log.With(zap.String("svc", "checks")), m.kvStore, m.kvService, m.kvService) + checkSvc = checks.NewService(m.log.With(zap.String("svc", "checks")), m.kvStore, ts.OrganizationService, m.kvService) checkSvc = middleware.NewCheckService(checkSvc, m.kvService, coordinator) } @@ -1111,8 +1122,7 @@ func (m *Launcher) run(ctx context.Context) (err error) { m.log.Error("Failed creating new labels store", zap.Error(err)) return err } - ls := label.NewService(labelsStore) - labelSvc = label.NewLabelController(m.flagger, m.kvService, ls) + labelSvc = label.NewService(labelsStore) } ts.BucketService = storage.NewBucketService(m.log, ts.BucketService, m.engine) @@ -1228,7 +1238,6 @@ func (m *Launcher) run(ctx context.Context) (err error) { ChronografService: chronografSvc, SecretService: secretSvc, LookupService: resourceResolver, - DocumentService: m.kvService, OrgLookupService: resourceResolver, WriteEventRecorder: infprom.NewEventRecorder("write"), QueryEventRecorder: infprom.NewEventRecorder("query"), @@ -1252,7 +1261,7 @@ func (m *Launcher) run(ctx context.Context) (err error) { pkger.WithBucketSVC(authorizer.NewBucketService(b.BucketService)), pkger.WithCheckSVC(authorizer.NewCheckService(b.CheckService, authedUrmSVC, authedOrgSVC)), pkger.WithDashboardSVC(authorizer.NewDashboardService(b.DashboardService)), - pkger.WithLabelSVC(authorizer.NewLabelServiceWithOrg(b.LabelService, b.OrgLookupService)), + pkger.WithLabelSVC(label.NewAuthedLabelService(labelSvc, b.OrgLookupService)), pkger.WithNotificationEndpointSVC(authorizer.NewNotificationEndpointService(b.NotificationEndpointService, authedUrmSVC, authedOrgSVC)), pkger.WithNotificationRuleSVC(authorizer.NewNotificationRuleStore(b.NotificationRuleStore, authedUrmSVC, authedOrgSVC)), pkger.WithOrganizationService(authorizer.NewOrgService(b.OrganizationService)), @@ -1283,12 +1292,9 @@ func (m *Launcher) run(ctx context.Context) (err error) { onboardHTTPServer := tenant.NewHTTPOnboardHandler(m.log, onboardSvc) // feature flagging for new labels service - var oldLabelHandler nethttp.Handler var labelHandler *label.LabelHandler { b := m.apibackend - labelSvcWithOrg := authorizer.NewLabelServiceWithOrg(labelSvc, b.OrgLookupService) - oldLabelHandler = http.NewLabelHandler(m.log.With(zap.String("handler", "labels")), labelSvcWithOrg, kithttp.ErrorHandler(0)) labelSvc = label.NewAuthedLabelService(labelSvc, b.OrgLookupService) labelSvc = label.NewLabelLogger(m.log.With(zap.String("handler", "labels")), labelSvc) @@ -1363,7 +1369,7 @@ func (m *Launcher) run(ctx context.Context) (err error) { http.WithResourceHandler(templatesHTTPServer), http.WithResourceHandler(onboardHTTPServer), http.WithResourceHandler(authHTTPServer), - http.WithResourceHandler(kithttp.NewFeatureHandler(feature.NewLabelPackage(), m.flagger, oldLabelHandler, labelHandler, labelHandler.Prefix())), + http.WithResourceHandler(labelHandler), http.WithResourceHandler(sessionHTTPServer.SignInResourceHandler()), http.WithResourceHandler(sessionHTTPServer.SignOutResourceHandler()), http.WithResourceHandler(userHTTPServer.MeResourceHandler()), diff --git a/cmd/influxd/launcher/launcher_helpers.go b/cmd/influxd/launcher/launcher_helpers.go index f9b33c6aba..5ff8cea484 100644 --- a/cmd/influxd/launcher/launcher_helpers.go +++ b/cmd/influxd/launcher/launcher_helpers.go @@ -22,10 +22,12 @@ import ( dashboardTransport "github.com/influxdata/influxdb/v2/dashboards/transport" "github.com/influxdata/influxdb/v2/http" "github.com/influxdata/influxdb/v2/kit/feature" + "github.com/influxdata/influxdb/v2/label" "github.com/influxdata/influxdb/v2/mock" "github.com/influxdata/influxdb/v2/pkg/httpc" "github.com/influxdata/influxdb/v2/pkger" "github.com/influxdata/influxdb/v2/query" + "github.com/influxdata/influxdb/v2/tenant" dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" ) @@ -363,9 +365,9 @@ func (tl *TestLauncher) FluxQueryService() *http.FluxQueryService { return &http.FluxQueryService{Addr: tl.URL(), Token: tl.Auth.Token} } -func (tl *TestLauncher) BucketService(tb testing.TB) *http.BucketService { +func (tl *TestLauncher) BucketService(tb testing.TB) *tenant.BucketClientService { tb.Helper() - return &http.BucketService{Client: tl.HTTPClient(tb)} + return &tenant.BucketClientService{Client: tl.HTTPClient(tb)} } func (tl *TestLauncher) DashboardService(tb testing.TB) influxdb.DashboardService { @@ -373,9 +375,9 @@ func (tl *TestLauncher) DashboardService(tb testing.TB) influxdb.DashboardServic return &dashboardTransport.DashboardService{Client: tl.HTTPClient(tb)} } -func (tl *TestLauncher) LabelService(tb testing.TB) *http.LabelService { +func (tl *TestLauncher) LabelService(tb testing.TB) influxdb.LabelService { tb.Helper() - return &http.LabelService{Client: tl.HTTPClient(tb)} + return &label.LabelClientService{Client: tl.HTTPClient(tb)} } func (tl *TestLauncher) NotificationEndpointService(tb testing.TB) *http.NotificationEndpointService { @@ -389,7 +391,8 @@ func (tl *TestLauncher) NotificationRuleService(tb testing.TB) influxdb.Notifica } func (tl *TestLauncher) OrgService(tb testing.TB) influxdb.OrganizationService { - return tl.kvService + tb.Helper() + return &tenant.OrgClientService{Client: tl.HTTPClient(tb)} } func (tl *TestLauncher) PkgerService(tb testing.TB) pkger.SVC { diff --git a/cmd/influxd/launcher/launcher_test.go b/cmd/influxd/launcher/launcher_test.go index ba52c45679..9032c98037 100644 --- a/cmd/influxd/launcher/launcher_test.go +++ b/cmd/influxd/launcher/launcher_test.go @@ -9,8 +9,9 @@ import ( platform "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/cmd/influxd/launcher" - "github.com/influxdata/influxdb/v2/http" _ "github.com/influxdata/influxdb/v2/fluxinit/static" + "github.com/influxdata/influxdb/v2/http" + "github.com/influxdata/influxdb/v2/tenant" ) // Default context. @@ -23,7 +24,12 @@ func TestLauncher_Setup(t *testing.T) { } defer l.Shutdown(ctx) - svc := &http.SetupService{Addr: l.URL()} + client, err := http.NewHTTPClient(l.URL(), "", false) + if err != nil { + t.Fatal(err) + } + + svc := &tenant.OnboardClientService{Client: client} if results, err := svc.OnboardInitialUser(ctx, &platform.OnboardingRequest{ User: "USER", Password: "PASSWORD", diff --git a/cmd/influxd/launcher/pkger_test.go b/cmd/influxd/launcher/pkger_test.go index 833d6daa60..d5cf1bc2a8 100644 --- a/cmd/influxd/launcher/pkger_test.go +++ b/cmd/influxd/launcher/pkger_test.go @@ -827,7 +827,7 @@ func TestLauncher_Pkger(t *testing.T) { pkger.WithLabelSVC(&fakeLabelSVC{ // can't use the LabelService HTTP client b/c it doesn't cover the // all the resources pkger supports... :sadpanda: - LabelService: l.kvService, + LabelService: l.LabelService(t), createKillCount: -1, deleteKillCount: 3, }), @@ -866,6 +866,7 @@ func TestLauncher_Pkger(t *testing.T) { ResourceType: res.resourceType, }) require.NoError(t, err) + assert.Len(t, mappedLabels, 1, res.resourceType) if len(mappedLabels) == 1 { assert.Equal(t, sum.Labels[0].ID, pkger.SafeID(mappedLabels[0].ID)) @@ -1561,7 +1562,7 @@ func TestLauncher_Pkger(t *testing.T) { require.Len(t, updateSum.Labels, 1) initial, updated := initialSum.Labels[0], updateSum.Labels[0] - assert.NotEqual(t, initial.ID, updated.ID) + assert.NotEqual(t, initial.ID, updated.ID, "label ID should be different") initial.ID, updated.ID = 0, 0 assert.Equal(t, initial, updated) }) @@ -2246,11 +2247,7 @@ func TestLauncher_Pkger(t *testing.T) { stack, initialSummary, cleanup := newLabelAssociationTestFn(t) defer cleanup() - // TODO(jsteenb2): cannot figure out the issue with the http.LabelService returning - // the bad HTTP method error :-(. Revisit this and replace with the - // the HTTP client when possible. Goal is to remove the kvservice - // dep here. - err := l.kvService.DeleteLabelMapping(ctx, &influxdb.LabelMapping{ + err := l.LabelService(t).DeleteLabelMapping(ctx, &influxdb.LabelMapping{ LabelID: influxdb.ID(initialSummary.Labels[0].ID), ResourceID: influxdb.ID(initialSummary.Buckets[0].ID), ResourceType: influxdb.BucketsResourceType, @@ -2274,14 +2271,10 @@ func TestLauncher_Pkger(t *testing.T) { OrgID: l.Org.ID, Name: "test-label-2", } - require.NoError(t, l.kvService.CreateLabel(ctx, newLabel)) + require.NoError(t, l.LabelService(t).CreateLabel(ctx, newLabel)) defer resourceCheck.mustDeleteLabel(t, newLabel.ID) - // TODO(jsteenb2): cannot figure out the issue with the http.LabelService returning - // the bad HTTP method error :-(. Revisit this and replace with the - // the HTTP client when possible. Goal is to remove the kvservice - // dep here. - err := l.kvService.CreateLabelMapping(ctx, &influxdb.LabelMapping{ + err := l.LabelService(t).CreateLabelMapping(ctx, &influxdb.LabelMapping{ LabelID: newLabel.ID, ResourceID: influxdb.ID(initialSummary.Buckets[0].ID), ResourceType: influxdb.BucketsResourceType, diff --git a/cmd/influxd/upgrade/database_test.go b/cmd/influxd/upgrade/database_test.go index b482c2a6e4..b15a27812c 100644 --- a/cmd/influxd/upgrade/database_test.go +++ b/cmd/influxd/upgrade/database_test.go @@ -2,7 +2,6 @@ package upgrade import ( "context" - "github.com/dustin/go-humanize" "io/ioutil" "net/http" "net/url" @@ -11,6 +10,8 @@ import ( "strconv" "testing" + "github.com/dustin/go-humanize" + "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/bolt" "github.com/influxdata/influxdb/v2/cmd/influxd/launcher" @@ -161,7 +162,7 @@ func TestUpgradeRealDB(t *testing.T) { } } - auths, _, err := v2.kvService.FindAuthorizations(ctx, influxdb.AuthorizationFilter{}) + auths, _, err := v2.authSvcV2.FindAuthorizations(ctx, influxdb.AuthorizationFilter{}) require.Nil(t, err) require.Len(t, auths, 1) diff --git a/cmd/influxd/upgrade/security_test.go b/cmd/influxd/upgrade/security_test.go index dba9905137..127e1751cc 100644 --- a/cmd/influxd/upgrade/security_test.go +++ b/cmd/influxd/upgrade/security_test.go @@ -10,8 +10,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" "github.com/influxdata/influxdb/v2/inmem" - "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration" "github.com/influxdata/influxdb/v2/kv/migration/all" "github.com/influxdata/influxdb/v2/tenant" @@ -162,13 +162,22 @@ func TestUpgradeSecurity(t *testing.T) { require.NoError(t, err) err = migrator.Up(ctx) require.NoError(t, err) - authStore, err := authv1.NewStore(kvStore) + + authStoreV1, err := authv1.NewStore(kvStore) require.NoError(t, err) + tenantStore := tenant.NewStore(kvStore) tenantSvc := tenant.NewService(tenantStore) + + authStoreV2, err := authorization.NewStore(kvStore) + require.NoError(t, err) + v2 := &influxDBv2{ - authSvc: authv1.NewService(authStore, tenantSvc), - onboardSvc: tenant.NewOnboardService(tenantSvc, kv.NewService(zap.NewNop(), kvStore, kv.ServiceConfig{})), + authSvc: authv1.NewService(authStoreV1, tenantSvc), + onboardSvc: tenant.NewOnboardService( + tenantSvc, + authorization.NewService(authStoreV2, tenantSvc), + ), } // onboard admin diff --git a/cmd/influxd/upgrade/upgrade.go b/cmd/influxd/upgrade/upgrade.go index 79579e9f38..bd4554ef8f 100644 --- a/cmd/influxd/upgrade/upgrade.go +++ b/cmd/influxd/upgrade/upgrade.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" "github.com/influxdata/influxdb/v2/bolt" "github.com/influxdata/influxdb/v2/dbrp" "github.com/influxdata/influxdb/v2/fluxinit" @@ -275,7 +276,7 @@ type influxDBv2 struct { bucketSvc influxdb.BucketService onboardSvc influxdb.OnboardingService authSvc *authv1.Service - kvService *kv.Service + authSvcV2 influxdb.AuthorizationService meta *meta.Client } @@ -478,10 +479,6 @@ func newInfluxDBv2(ctx context.Context, opts *optionsV2, log *zap.Logger) (svc * svc = &influxDBv2{} svc.log = log - // ********************* - // V2 specific services - serviceConfig := kv.ServiceConfig{} - // Create BoltDB store and K/V service svc.boltClient = bolt.NewClient(log.With(zap.String("service", "bolt"))) svc.boltClient.Path = opts.boltPath @@ -493,7 +490,6 @@ func newInfluxDBv2(ctx context.Context, opts *optionsV2, log *zap.Logger) (svc * svc.store = bolt.NewKVStore(log.With(zap.String("service", "kvstore-bolt")), opts.boltPath) svc.store.WithDB(svc.boltClient.DB()) svc.kvStore = svc.store - svc.kvService = kv.NewService(log.With(zap.String("store", "kv")), svc.store, serviceConfig) // ensure migrator is run migrator, err := migration.NewMigrator( @@ -512,11 +508,6 @@ func newInfluxDBv2(ctx context.Context, opts *optionsV2, log *zap.Logger) (svc * return nil, err } - // other required services - var ( - authSvc influxdb.AuthorizationService = svc.kvService - ) - // Create Tenant service (orgs, buckets, ) svc.tenantStore = tenant.NewStore(svc.kvStore) svc.ts = tenant.NewSystem(svc.tenantStore, log.With(zap.String("store", "new")), reg, metric.WithSuffix("new")) @@ -537,15 +528,24 @@ func newInfluxDBv2(ctx context.Context, opts *optionsV2, log *zap.Logger) (svc * ) svc.ts.BucketService = storage.NewBucketService(log, svc.ts.BucketService, engine) - // on-boarding service (influx setup) - svc.onboardSvc = tenant.NewOnboardService(svc.ts, authSvc) - // v1 auth service - authStore, err := authv1.NewStore(svc.kvStore) + authStoreV2, err := authorization.NewStore(svc.store) if err != nil { return nil, err } - svc.authSvc = authv1.NewService(authStore, svc.ts) + + svc.authSvcV2 = authorization.NewService(authStoreV2, svc.ts) + + // on-boarding service (influx setup) + svc.onboardSvc = tenant.NewOnboardService(svc.ts, svc.authSvcV2) + + // v1 auth service + authStoreV1, err := authv1.NewStore(svc.kvStore) + if err != nil { + return nil, err + } + + svc.authSvc = authv1.NewService(authStoreV1, svc.ts) return svc, nil } diff --git a/dashboards/service_test.go b/dashboards/service_test.go index b76e2ae929..ce5b52bcfe 100644 --- a/dashboards/service_test.go +++ b/dashboards/service_test.go @@ -12,6 +12,7 @@ import ( dashboardtesting "github.com/influxdata/influxdb/v2/dashboards/testing" "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration/all" + "github.com/influxdata/influxdb/v2/mock" "go.uber.org/zap/zaptest" ) @@ -38,7 +39,7 @@ func initDashboardService(s kv.SchemaStore, f dashboardtesting.DashboardFields, } ctx := context.Background() - kvSvc := kv.NewService(zaptest.NewLogger(t), s) + kvSvc := kv.NewService(zaptest.NewLogger(t), s, &mock.OrganizationService{}) kvSvc.IDGenerator = f.IDGenerator kvSvc.TimeGenerator = f.TimeGenerator diff --git a/dashboards/testing/util.go b/dashboards/testing/util.go index 1caea3b44f..ea0cb3e971 100644 --- a/dashboards/testing/util.go +++ b/dashboards/testing/util.go @@ -4,8 +4,7 @@ import ( "context" "testing" - platform "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2" ) // TODO(goller): remove opPrefix argument @@ -29,14 +28,14 @@ func ErrorsEqual(t *testing.T, actual, expected error) { t.Errorf("expected error %s but received nil", expected.Error()) } - if platform.ErrorCode(expected) != platform.ErrorCode(actual) { + if influxdb.ErrorCode(expected) != influxdb.ErrorCode(actual) { t.Logf("\nexpected: %v\nactual: %v\n\n", expected, actual) - t.Errorf("expected error code %q but received %q", platform.ErrorCode(expected), platform.ErrorCode(actual)) + t.Errorf("expected error code %q but received %q", influxdb.ErrorCode(expected), influxdb.ErrorCode(actual)) } - if platform.ErrorMessage(expected) != platform.ErrorMessage(actual) { + if influxdb.ErrorMessage(expected) != influxdb.ErrorMessage(actual) { t.Logf("\nexpected: %v\nactual: %v\n\n", expected, actual) - t.Errorf("expected error message %q but received %q", platform.ErrorMessage(expected), platform.ErrorMessage(actual)) + t.Errorf("expected error message %q but received %q", influxdb.ErrorMessage(expected), influxdb.ErrorMessage(actual)) } } @@ -47,13 +46,13 @@ func FloatPtr(f float64) *float64 { return p } -func idPtr(id platform.ID) *platform.ID { +func idPtr(id influxdb.ID) *influxdb.ID { return &id } // MustIDBase16 is an helper to ensure a correct ID is built during testing. -func MustIDBase16(s string) platform.ID { - id, err := platform.IDFromString(s) +func MustIDBase16(s string) influxdb.ID { + id, err := influxdb.IDFromString(s) if err != nil { panic(err) } @@ -61,12 +60,12 @@ func MustIDBase16(s string) platform.ID { } // MustIDBase16Ptr is an helper to ensure a correct ID ptr ref is built during testing. -func MustIDBase16Ptr(s string) *platform.ID { +func MustIDBase16Ptr(s string) *influxdb.ID { id := MustIDBase16(s) return &id } -func MustCreateOrgs(ctx context.Context, svc *kv.Service, os ...*platform.Organization) { +func MustCreateOrgs(ctx context.Context, svc influxdb.OrganizationService, os ...*influxdb.Organization) { for _, o := range os { if err := svc.CreateOrganization(ctx, o); err != nil { panic(err) @@ -74,7 +73,7 @@ func MustCreateOrgs(ctx context.Context, svc *kv.Service, os ...*platform.Organi } } -func MustCreateLabels(ctx context.Context, svc *kv.Service, labels ...*platform.Label) { +func MustCreateLabels(ctx context.Context, svc influxdb.LabelService, labels ...*influxdb.Label) { for _, l := range labels { if err := svc.CreateLabel(ctx, l); err != nil { panic(err) @@ -82,7 +81,7 @@ func MustCreateLabels(ctx context.Context, svc *kv.Service, labels ...*platform. } } -func MustCreateUsers(ctx context.Context, svc *kv.Service, us ...*platform.User) { +func MustCreateUsers(ctx context.Context, svc influxdb.UserService, us ...*influxdb.User) { for _, u := range us { if err := svc.CreateUser(ctx, u); err != nil { panic(err) @@ -90,7 +89,7 @@ func MustCreateUsers(ctx context.Context, svc *kv.Service, us ...*platform.User) } } -func MustCreateMappings(ctx context.Context, svc *kv.Service, ms ...*platform.UserResourceMapping) { +func MustCreateMappings(ctx context.Context, svc influxdb.UserResourceMappingService, ms ...*influxdb.UserResourceMapping) { for _, m := range ms { if err := svc.CreateUserResourceMapping(ctx, m); err != nil { panic(err) @@ -98,34 +97,34 @@ func MustCreateMappings(ctx context.Context, svc *kv.Service, ms ...*platform.Us } } -func MustMakeUsersOrgOwner(ctx context.Context, svc *kv.Service, oid platform.ID, uids ...platform.ID) { - ms := make([]*platform.UserResourceMapping, len(uids)) +func MustMakeUsersOrgOwner(ctx context.Context, svc influxdb.UserResourceMappingService, oid influxdb.ID, uids ...influxdb.ID) { + ms := make([]*influxdb.UserResourceMapping, len(uids)) for i, uid := range uids { - ms[i] = &platform.UserResourceMapping{ + ms[i] = &influxdb.UserResourceMapping{ UserID: uid, - UserType: platform.Owner, - ResourceType: platform.OrgsResourceType, + UserType: influxdb.Owner, + ResourceType: influxdb.OrgsResourceType, ResourceID: oid, } } MustCreateMappings(ctx, svc, ms...) } -func MustMakeUsersOrgMember(ctx context.Context, svc *kv.Service, oid platform.ID, uids ...platform.ID) { - ms := make([]*platform.UserResourceMapping, len(uids)) +func MustMakeUsersOrgMember(ctx context.Context, svc influxdb.UserResourceMappingService, oid influxdb.ID, uids ...influxdb.ID) { + ms := make([]*influxdb.UserResourceMapping, len(uids)) for i, uid := range uids { - ms[i] = &platform.UserResourceMapping{ + ms[i] = &influxdb.UserResourceMapping{ UserID: uid, - UserType: platform.Member, - ResourceType: platform.OrgsResourceType, + UserType: influxdb.Member, + ResourceType: influxdb.OrgsResourceType, ResourceID: oid, } } MustCreateMappings(ctx, svc, ms...) } -func MustNewPermissionAtID(id platform.ID, a platform.Action, rt platform.ResourceType, orgID platform.ID) *platform.Permission { - perm, err := platform.NewPermissionAtID(id, a, rt, orgID) +func MustNewPermissionAtID(id influxdb.ID, a influxdb.Action, rt influxdb.ResourceType, orgID influxdb.ID) *influxdb.Permission { + perm, err := influxdb.NewPermissionAtID(id, a, rt, orgID) if err != nil { panic(err) } diff --git a/dashboards/transport/http_test.go b/dashboards/transport/http_test.go index fa2978122e..c3e23e5187 100644 --- a/dashboards/transport/http_test.go +++ b/dashboards/transport/http_test.go @@ -1784,7 +1784,7 @@ func initDashboardService(f dashboardstesting.DashboardFields, t *testing.T) (in log := zaptest.NewLogger(t) store := newTestInmemStore(t) - kvsvc := kv.NewService(log, store) + kvsvc := kv.NewService(log, store, &mock.OrganizationService{}) kvsvc.IDGenerator = f.IDGenerator svc := dashboards.NewService( diff --git a/http/api_handler.go b/http/api_handler.go index d8d5376c11..272a6376ed 100644 --- a/http/api_handler.go +++ b/http/api_handler.go @@ -1,9 +1,11 @@ package http import ( + "context" "net/http" "github.com/go-chi/chi" + "github.com/influxdata/httprouter" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/authorizer" "github.com/influxdata/influxdb/v2/chronograf/server" @@ -142,10 +144,6 @@ func NewAPIHandler(b *APIBackend, opts ...APIHandlerOptFn) *APIHandler { deleteBackend := NewDeleteBackend(b.Logger.With(zap.String("handler", "delete")), b) h.Mount(prefixDelete, NewDeleteHandler(b.Logger, deleteBackend)) - documentBackend := NewDocumentBackend(b.Logger.With(zap.String("handler", "document")), b) - documentBackend.DocumentService = authorizer.NewDocumentService(b.DocumentService) - h.Mount(prefixDocuments, NewDocumentHandler(documentBackend)) - fluxBackend := NewFluxBackend(b.Logger.With(zap.String("handler", "query")), b) h.Mount(prefixQuery, NewFluxHandler(b.Logger, fluxBackend)) @@ -268,3 +266,25 @@ func serveLinksHandler(errorHandler influxdb.HTTPErrorHandler) http.Handler { } return http.HandlerFunc(fn) } + +func decodeIDFromCtx(ctx context.Context, name string) (influxdb.ID, error) { + params := httprouter.ParamsFromContext(ctx) + idStr := params.ByName(name) + + if idStr == "" { + return 0, &influxdb.Error{ + Code: influxdb.EInvalid, + Msg: "url missing " + name, + } + } + + var i influxdb.ID + if err := i.DecodeFromString(idStr); err != nil { + return 0, &influxdb.Error{ + Code: influxdb.EInvalid, + Err: err, + } + } + + return i, nil +} diff --git a/http/api_handler_test.go b/http/api_handler_test.go index 522bba20ec..e0c7b85db4 100644 --- a/http/api_handler_test.go +++ b/http/api_handler_test.go @@ -10,7 +10,7 @@ import ( "github.com/google/go-cmp/cmp" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2/pkg/httpc" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" "go.uber.org/zap/zaptest" @@ -120,8 +120,12 @@ func jsonEqual(s1, s2 string) (eq bool, diff string, err error) { return cmp.Equal(o1, o2), diff, err } -func newInMemKVSVC(t *testing.T) *kv.Service { +func mustNewHTTPClient(t *testing.T, addr, token string) *httpc.Client { t.Helper() - return kv.NewService(zaptest.NewLogger(t), NewTestInmemStore(t)) + httpClient, err := NewHTTPClient(addr, token, false) + if err != nil { + t.Fatal(err) + } + return httpClient } diff --git a/http/auth_test.go b/http/auth_test.go index d0c7c0099a..4b53a99f27 100644 --- a/http/auth_test.go +++ b/http/auth_test.go @@ -12,10 +12,12 @@ import ( "github.com/influxdata/httprouter" platform "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" pcontext "github.com/influxdata/influxdb/v2/context" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/mock" + "github.com/influxdata/influxdb/v2/tenant" platformtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -869,8 +871,17 @@ func initAuthorizationService(f platformtesting.AuthorizationFields, t *testing. } store := NewTestInmemStore(t) - svc := kv.NewService(zaptest.NewLogger(t), store) - svc.OrgIDs = f.OrgIDGenerator + tenantStore := tenant.NewStore(store) + tenantStore.OrgIDGen = f.OrgIDGenerator + tenantService := tenant.NewService(tenantStore) + + authStore, err := authorization.NewStore(store) + if err != nil { + t.Fatal(err) + } + authService := authorization.NewService(authStore, tenantService) + + svc := kv.NewService(zaptest.NewLogger(t), store, tenantService) svc.IDGenerator = f.IDGenerator svc.TokenGenerator = f.TokenGenerator svc.TimeGenerator = f.TimeGenerator @@ -878,14 +889,13 @@ func initAuthorizationService(f platformtesting.AuthorizationFields, t *testing. ctx := context.Background() for _, u := range f.Users { - if err := svc.PutUser(ctx, u); err != nil { + if err := tenantService.CreateUser(ctx, u); err != nil { t.Fatalf("failed to populate users") } } for _, o := range f.Orgs { - o.ID = svc.OrgIDs.ID() - if err := svc.PutOrganization(ctx, o); err != nil { + if err := tenantService.CreateOrganization(ctx, o); err != nil { t.Fatalf("failed to populate orgs") } } @@ -893,7 +903,7 @@ func initAuthorizationService(f platformtesting.AuthorizationFields, t *testing. var token string for _, a := range f.Authorizations { - if err := svc.PutAuthorization(ctx, a); err != nil { + if err := authService.CreateAuthorization(ctx, a); err != nil { t.Fatalf("failed to populate authorizations") } @@ -908,9 +918,9 @@ func initAuthorizationService(f platformtesting.AuthorizationFields, t *testing. authorizationBackend := NewMockAuthorizationBackend(t) authorizationBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - authorizationBackend.AuthorizationService = svc + authorizationBackend.AuthorizationService = authService authorizationBackend.UserService = mus - authorizationBackend.OrganizationService = svc + authorizationBackend.OrganizationService = tenantService authorizationBackend.LookupService = &mock.LookupService{ NameFn: func(ctx context.Context, resource platform.ResourceType, id platform.ID) (string, error) { switch resource { @@ -925,7 +935,7 @@ func initAuthorizationService(f platformtesting.AuthorizationFields, t *testing. authZ := NewAuthorizationHandler(zaptest.NewLogger(t), authorizationBackend) authN := NewAuthenticationHandler(zaptest.NewLogger(t), kithttp.ErrorHandler(0)) - authN.AuthorizationService = svc + authN.AuthorizationService = authService authN.Handler = authZ authN.UserService = mus diff --git a/http/bucket_service.go b/http/bucket_service.go deleted file mode 100644 index 1819a88749..0000000000 --- a/http/bucket_service.go +++ /dev/null @@ -1,752 +0,0 @@ -package http - -import ( - "context" - "fmt" - "net/http" - "path" - "strings" - "time" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kit/tracing" - kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/pkg/httpc" - "go.uber.org/zap" -) - -// BucketBackend is all services and associated parameters required to construct -// the BucketHandler. -type BucketBackend struct { - log *zap.Logger - influxdb.HTTPErrorHandler - - BucketService influxdb.BucketService - BucketOperationLogService influxdb.BucketOperationLogService - UserResourceMappingService influxdb.UserResourceMappingService - LabelService influxdb.LabelService - UserService influxdb.UserService - OrganizationService influxdb.OrganizationService -} - -// NewBucketBackend returns a new instance of BucketBackend. -func NewBucketBackend(log *zap.Logger, b *APIBackend) *BucketBackend { - return &BucketBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: log, - - BucketService: b.BucketService, - BucketOperationLogService: b.BucketOperationLogService, - UserResourceMappingService: b.UserResourceMappingService, - LabelService: b.LabelService, - UserService: b.UserService, - OrganizationService: b.OrganizationService, - } -} - -// BucketHandler represents an HTTP API handler for buckets. -type BucketHandler struct { - *httprouter.Router - api *kithttp.API - log *zap.Logger - - BucketService influxdb.BucketService - BucketOperationLogService influxdb.BucketOperationLogService - UserResourceMappingService influxdb.UserResourceMappingService - LabelService influxdb.LabelService - UserService influxdb.UserService - OrganizationService influxdb.OrganizationService -} - -const ( - prefixBuckets = "/api/v2/buckets" - bucketsIDPath = "/api/v2/buckets/:id" - bucketsIDMembersPath = "/api/v2/buckets/:id/members" - bucketsIDMembersIDPath = "/api/v2/buckets/:id/members/:userID" - bucketsIDOwnersPath = "/api/v2/buckets/:id/owners" - bucketsIDOwnersIDPath = "/api/v2/buckets/:id/owners/:userID" - bucketsIDLabelsPath = "/api/v2/buckets/:id/labels" - bucketsIDLabelsIDPath = "/api/v2/buckets/:id/labels/:lid" -) - -// NewBucketHandler returns a new instance of BucketHandler. -func NewBucketHandler(log *zap.Logger, b *BucketBackend) *BucketHandler { - h := &BucketHandler{ - Router: NewRouter(b.HTTPErrorHandler), - api: kithttp.NewAPI(kithttp.WithLog(log)), - log: log, - - BucketService: b.BucketService, - BucketOperationLogService: b.BucketOperationLogService, - UserResourceMappingService: b.UserResourceMappingService, - LabelService: b.LabelService, - UserService: b.UserService, - OrganizationService: b.OrganizationService, - } - - h.HandlerFunc("POST", prefixBuckets, h.handlePostBucket) - h.HandlerFunc("GET", prefixBuckets, h.handleGetBuckets) - h.HandlerFunc("GET", bucketsIDPath, h.handleGetBucket) - h.HandlerFunc("PATCH", bucketsIDPath, h.handlePatchBucket) - h.HandlerFunc("DELETE", bucketsIDPath, h.handleDeleteBucket) - - memberBackend := MemberBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: b.log.With(zap.String("handler", "member")), - ResourceType: influxdb.BucketsResourceType, - UserType: influxdb.Member, - UserResourceMappingService: b.UserResourceMappingService, - UserService: b.UserService, - } - h.HandlerFunc("POST", bucketsIDMembersPath, newPostMemberHandler(memberBackend)) - h.HandlerFunc("GET", bucketsIDMembersPath, newGetMembersHandler(memberBackend)) - h.HandlerFunc("DELETE", bucketsIDMembersIDPath, newDeleteMemberHandler(memberBackend)) - - ownerBackend := MemberBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: b.log.With(zap.String("handler", "member")), - ResourceType: influxdb.BucketsResourceType, - UserType: influxdb.Owner, - UserResourceMappingService: b.UserResourceMappingService, - UserService: b.UserService, - } - h.HandlerFunc("POST", bucketsIDOwnersPath, newPostMemberHandler(ownerBackend)) - h.HandlerFunc("GET", bucketsIDOwnersPath, newGetMembersHandler(ownerBackend)) - h.HandlerFunc("DELETE", bucketsIDOwnersIDPath, newDeleteMemberHandler(ownerBackend)) - - labelBackend := &LabelBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: b.log.With(zap.String("handler", "label")), - LabelService: b.LabelService, - ResourceType: influxdb.BucketsResourceType, - } - h.HandlerFunc("GET", bucketsIDLabelsPath, newGetLabelsHandler(labelBackend)) - h.HandlerFunc("POST", bucketsIDLabelsPath, newPostLabelHandler(labelBackend)) - h.HandlerFunc("DELETE", bucketsIDLabelsIDPath, newDeleteLabelHandler(labelBackend)) - - return h -} - -// bucket is used for serialization/deserialization with duration string syntax. -type bucket struct { - ID influxdb.ID `json:"id,omitempty"` - OrgID influxdb.ID `json:"orgID,omitempty"` - Type string `json:"type"` - Description string `json:"description,omitempty"` - Name string `json:"name"` - RetentionPolicyName string `json:"rp,omitempty"` // This to support v1 sources - RetentionRules []retentionRule `json:"retentionRules"` - influxdb.CRUDLog -} - -// retentionRule is the retention rule action for a bucket. -type retentionRule struct { - Type string `json:"type"` - EverySeconds int64 `json:"everySeconds"` -} - -func (rr *retentionRule) RetentionPeriod() (time.Duration, error) { - t := time.Duration(rr.EverySeconds) * time.Second - if t < time.Second { - return t, &influxdb.Error{ - Code: influxdb.EUnprocessableEntity, - Msg: "expiration seconds must be greater than or equal to one second", - } - } - - return t, nil -} - -func (b *bucket) toInfluxDB() (*influxdb.Bucket, error) { - if b == nil { - return nil, nil - } - - var d time.Duration // zero value implies infinite retention policy - - // Only support a single retention period for the moment - if len(b.RetentionRules) > 0 { - d = time.Duration(b.RetentionRules[0].EverySeconds) * time.Second - if d < time.Second { - return nil, &influxdb.Error{ - Code: influxdb.EUnprocessableEntity, - Msg: "expiration seconds must be greater than or equal to one second", - } - } - } - - return &influxdb.Bucket{ - ID: b.ID, - OrgID: b.OrgID, - Type: influxdb.ParseBucketType(b.Type), - Description: b.Description, - Name: b.Name, - RetentionPolicyName: b.RetentionPolicyName, - RetentionPeriod: d, - CRUDLog: b.CRUDLog, - }, nil -} - -func newBucket(pb *influxdb.Bucket) *bucket { - if pb == nil { - return nil - } - - rules := []retentionRule{} - rp := int64(pb.RetentionPeriod.Round(time.Second) / time.Second) - if rp > 0 { - rules = append(rules, retentionRule{ - Type: "expire", - EverySeconds: rp, - }) - } - - return &bucket{ - ID: pb.ID, - OrgID: pb.OrgID, - Type: pb.Type.String(), - Name: pb.Name, - Description: pb.Description, - RetentionPolicyName: pb.RetentionPolicyName, - RetentionRules: rules, - CRUDLog: pb.CRUDLog, - } -} - -// bucketUpdate is used for serialization/deserialization with retention rules. -type bucketUpdate struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - RetentionRules []retentionRule `json:"retentionRules,omitempty"` -} - -func (b *bucketUpdate) OK() error { - if len(b.RetentionRules) > 0 { - _, err := b.RetentionRules[0].RetentionPeriod() - if err != nil { - return err - } - } - return nil -} - -func (b *bucketUpdate) toInfluxDB() *influxdb.BucketUpdate { - if b == nil { - return nil - } - - // For now, only use a single retention rule. - var d time.Duration - if len(b.RetentionRules) > 0 { - d, _ = b.RetentionRules[0].RetentionPeriod() - } - - return &influxdb.BucketUpdate{ - Name: b.Name, - Description: b.Description, - RetentionPeriod: &d, - } -} - -func newBucketUpdate(pb *influxdb.BucketUpdate) *bucketUpdate { - if pb == nil { - return nil - } - - up := &bucketUpdate{ - Name: pb.Name, - Description: pb.Description, - RetentionRules: []retentionRule{}, - } - - if pb.RetentionPeriod != nil { - d := int64((*pb.RetentionPeriod).Round(time.Second) / time.Second) - up.RetentionRules = append(up.RetentionRules, retentionRule{ - Type: "expire", - EverySeconds: d, - }) - } - return up -} - -type bucketResponse struct { - bucket - Links map[string]string `json:"links"` - Labels []influxdb.Label `json:"labels"` -} - -func NewBucketResponse(b *influxdb.Bucket, labels []*influxdb.Label) *bucketResponse { - res := &bucketResponse{ - Links: map[string]string{ - "labels": fmt.Sprintf("/api/v2/buckets/%s/labels", b.ID), - "logs": fmt.Sprintf("/api/v2/buckets/%s/logs", b.ID), - "members": fmt.Sprintf("/api/v2/buckets/%s/members", b.ID), - "org": fmt.Sprintf("/api/v2/orgs/%s", b.OrgID), - "owners": fmt.Sprintf("/api/v2/buckets/%s/owners", b.ID), - "self": fmt.Sprintf("/api/v2/buckets/%s", b.ID), - "write": fmt.Sprintf("/api/v2/write?org=%s&bucket=%s", b.OrgID, b.ID), - }, - bucket: *newBucket(b), - Labels: []influxdb.Label{}, - } - - for _, l := range labels { - res.Labels = append(res.Labels, *l) - } - - return res -} - -type bucketsResponse struct { - Links *influxdb.PagingLinks `json:"links"` - Buckets []*bucketResponse `json:"buckets"` -} - -func newBucketsResponse(ctx context.Context, opts influxdb.FindOptions, f influxdb.BucketFilter, bs []*influxdb.Bucket, labelService influxdb.LabelService) *bucketsResponse { - rs := make([]*bucketResponse, 0, len(bs)) - for _, b := range bs { - labels, _ := labelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: b.ID, ResourceType: influxdb.BucketsResourceType}) - rs = append(rs, NewBucketResponse(b, labels)) - } - return &bucketsResponse{ - Links: influxdb.NewPagingLinks(prefixBuckets, opts, f, len(bs)), - Buckets: rs, - } -} - -// handlePostBucket is the HTTP handler for the POST /api/v2/buckets route. -func (h *BucketHandler) handlePostBucket(w http.ResponseWriter, r *http.Request) { - var b postBucketRequest - if err := h.api.DecodeJSON(r.Body, &b); err != nil { - h.api.Err(w, r, err) - return - } - - bucket := b.toInfluxDB() - if err := h.BucketService.CreateBucket(r.Context(), bucket); err != nil { - h.api.Err(w, r, err) - return - } - h.log.Debug("Bucket created", zap.String("bucket", fmt.Sprint(bucket))) - - h.api.Respond(w, r, http.StatusCreated, NewBucketResponse(bucket, []*influxdb.Label{})) -} - -type postBucketRequest struct { - OrgID influxdb.ID `json:"orgID,omitempty"` - Name string `json:"name"` - Description string `json:"description"` - RetentionPolicyName string `json:"rp,omitempty"` // This to support v1 sources - RetentionRules []retentionRule `json:"retentionRules"` -} - -func (b *postBucketRequest) OK() error { - if !b.OrgID.Valid() { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "organization id must be provided", - } - } - - // Only support a single retention period for the moment - if len(b.RetentionRules) > 0 { - if _, err := b.RetentionRules[0].RetentionPeriod(); err != nil { - return &influxdb.Error{ - Code: influxdb.EUnprocessableEntity, - Msg: err.Error(), - } - } - } - - // names starting with an underscore are reserved for system buckets - if err := validBucketName(b.toInfluxDB()); err != nil { - return err - } - - return nil -} - -func (b postBucketRequest) toInfluxDB() *influxdb.Bucket { - // Only support a single retention period for the moment - var dur time.Duration - if len(b.RetentionRules) > 0 { - dur, _ = b.RetentionRules[0].RetentionPeriod() - } - - return &influxdb.Bucket{ - OrgID: b.OrgID, - Description: b.Description, - Name: b.Name, - Type: influxdb.BucketTypeUser, - RetentionPolicyName: b.RetentionPolicyName, - RetentionPeriod: dur, - } -} - -// handleGetBucket is the HTTP handler for the GET /api/v2/buckets/:id route. -func (h *BucketHandler) handleGetBucket(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - id, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.api.Err(w, r, err) - return - } - - b, err := h.BucketService.FindBucketByID(ctx, id) - if err != nil { - h.api.Err(w, r, err) - return - } - - labels, err := h.LabelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: b.ID, ResourceType: influxdb.BucketsResourceType}) - if err != nil { - h.api.Err(w, r, err) - return - } - - h.log.Debug("Bucket retrieved", zap.String("bucket", fmt.Sprint(b))) - - h.api.Respond(w, r, http.StatusOK, NewBucketResponse(b, labels)) -} - -func bucketIDPath(id influxdb.ID) string { - return path.Join(prefixBuckets, id.String()) -} - -// handleDeleteBucket is the HTTP handler for the DELETE /api/v2/buckets/:id route. -func (h *BucketHandler) handleDeleteBucket(w http.ResponseWriter, r *http.Request) { - id, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.api.Err(w, r, err) - return - } - - if err := h.BucketService.DeleteBucket(r.Context(), id); err != nil { - h.api.Err(w, r, err) - return - } - - h.log.Debug("Bucket deleted", zap.String("bucketID", id.String())) - - h.api.Respond(w, r, http.StatusNoContent, nil) -} - -// handleGetBuckets is the HTTP handler for the GET /api/v2/buckets route. -func (h *BucketHandler) handleGetBuckets(w http.ResponseWriter, r *http.Request) { - var filter influxdb.BucketFilter - q := r.URL.Query() - if org := q.Get("org"); org != "" { - filter.Org = &org - } - if name := q.Get("name"); name != "" { - filter.Name = &name - } - - bucketID, err := decodeIDFromQuery(q, "id") - if err != nil { - h.api.Err(w, r, err) - return - } - if bucketID > 0 { - filter.ID = &bucketID - } - - orgID, err := decodeIDFromQuery(q, "orgID") - if err != nil { - h.api.Err(w, r, err) - return - } - if orgID > 0 { - filter.OrganizationID = &orgID - } - - opts, err := influxdb.DecodeFindOptions(r) - if err != nil { - h.api.Err(w, r, err) - return - } - - bs, _, err := h.BucketService.FindBuckets(r.Context(), filter, *opts) - if err != nil { - h.api.Err(w, r, err) - return - } - h.log.Debug("Buckets retrieved", zap.String("buckets", fmt.Sprint(bs))) - - h.api.Respond(w, r, http.StatusOK, newBucketsResponse(r.Context(), *opts, filter, bs, h.LabelService)) -} - -type getBucketsRequest struct { - filter influxdb.BucketFilter - opts influxdb.FindOptions -} - -func decodeGetBucketsRequest(r *http.Request) (*getBucketsRequest, error) { - qp := r.URL.Query() - req := &getBucketsRequest{} - - opts, err := influxdb.DecodeFindOptions(r) - if err != nil { - return nil, err - } - - req.opts = *opts - - if orgID := qp.Get("orgID"); orgID != "" { - id, err := influxdb.IDFromString(orgID) - if err != nil { - return nil, err - } - req.filter.OrganizationID = id - } - - if org := qp.Get("org"); org != "" { - req.filter.Org = &org - } - - if name := qp.Get("name"); name != "" { - req.filter.Name = &name - } - - if bucketID := qp.Get("id"); bucketID != "" { - id, err := influxdb.IDFromString(bucketID) - if err != nil { - return nil, err - } - req.filter.ID = id - } - - return req, nil -} - -// handlePatchBucket is the HTTP handler for the PATCH /api/v2/buckets route. -func (h *BucketHandler) handlePatchBucket(w http.ResponseWriter, r *http.Request) { - id, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.api.Err(w, r, err) - return - } - - var reqBody bucketUpdate - if err := h.api.DecodeJSON(r.Body, &reqBody); err != nil { - h.api.Err(w, r, err) - return - } - - if reqBody.Name != nil { - b, err := h.BucketService.FindBucketByID(r.Context(), id) - if err != nil { - h.api.Err(w, r, err) - return - } - b.Name = *reqBody.Name - if err := validBucketName(b); err != nil { - h.api.Err(w, r, err) - return - } - } - - b, err := h.BucketService.UpdateBucket(r.Context(), id, *reqBody.toInfluxDB()) - if err != nil { - h.api.Err(w, r, err) - return - } - - // TODO: should move to service to encapsulate labels and what any other dependencies. Future - // work for service definition - labels, err := h.LabelService.FindResourceLabels(r.Context(), influxdb.LabelMappingFilter{ - ResourceID: b.ID, - ResourceType: influxdb.BucketsResourceType, - }) - if err != nil { - h.api.Err(w, r, err) - return - } - h.log.Debug("Bucket updated", zap.String("bucket", fmt.Sprint(b))) - - h.api.Respond(w, r, http.StatusOK, NewBucketResponse(b, labels)) -} - -// BucketService connects to Influx via HTTP using tokens to manage buckets -type BucketService struct { - Client *httpc.Client - // OpPrefix is an additional property for error - // find bucket service, when finds nothing. - OpPrefix string -} - -// FindBucketByName returns a single bucket by name -func (s *BucketService) FindBucketByName(ctx context.Context, orgID influxdb.ID, name string) (*influxdb.Bucket, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - if name == "" { - return nil, &influxdb.Error{ - Code: influxdb.EUnprocessableEntity, - Op: s.OpPrefix + influxdb.OpFindBuckets, - Msg: "bucket name is required", - } - } - - bkts, n, err := s.FindBuckets(ctx, influxdb.BucketFilter{ - Name: &name, - OrganizationID: &orgID, - }) - if err != nil { - return nil, err - } - if n == 0 || len(bkts) == 0 { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Op: s.OpPrefix + influxdb.OpFindBucket, - Msg: fmt.Sprintf("bucket %q not found", name), - } - } - - return bkts[0], nil -} - -// FindBucketByID returns a single bucket by ID. -func (s *BucketService) FindBucketByID(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - // TODO(@jsteenb2): are tracing - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var br bucketResponse - err := s.Client. - Get(bucketIDPath(id)). - DecodeJSON(&br). - Do(ctx) - if err != nil { - return nil, err - } - return br.toInfluxDB() -} - -// FindBucket returns the first bucket that matches filter. -func (s *BucketService) FindBucket(ctx context.Context, filter influxdb.BucketFilter) (*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - bs, n, err := s.FindBuckets(ctx, filter) - if err != nil { - return nil, err - } - - if n == 0 && filter.Name != nil { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Op: s.OpPrefix + influxdb.OpFindBucket, - Msg: fmt.Sprintf("bucket %q not found", *filter.Name), - } - } else if n == 0 { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Op: s.OpPrefix + influxdb.OpFindBucket, - Msg: "bucket not found", - } - } - - return bs[0], nil -} - -// FindBuckets returns a list of buckets that match filter and the total count of matching buckets. -// Additional options provide pagination & sorting. -func (s *BucketService) FindBuckets(ctx context.Context, filter influxdb.BucketFilter, opt ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - params := influxdb.FindOptionParams(opt...) - if filter.OrganizationID != nil { - params = append(params, [2]string{"orgID", filter.OrganizationID.String()}) - } - if filter.Org != nil { - params = append(params, [2]string{"org", *filter.Org}) - } - if filter.ID != nil { - params = append(params, [2]string{"id", filter.ID.String()}) - } - if filter.Name != nil { - params = append(params, [2]string{"name", (*filter.Name)}) - } - - var bs bucketsResponse - err := s.Client. - Get(prefixBuckets). - QueryParams(params...). - DecodeJSON(&bs). - Do(ctx) - if err != nil { - return nil, 0, err - } - - buckets := make([]*influxdb.Bucket, 0, len(bs.Buckets)) - for _, b := range bs.Buckets { - pb, err := b.bucket.toInfluxDB() - if err != nil { - return nil, 0, err - } - - buckets = append(buckets, pb) - } - - return buckets, len(buckets), nil -} - -// CreateBucket creates a new bucket and sets b.ID with the new identifier. -func (s *BucketService) CreateBucket(ctx context.Context, b *influxdb.Bucket) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var br bucketResponse - err := s.Client. - PostJSON(newBucket(b), prefixBuckets). - DecodeJSON(&br). - Do(ctx) - if err != nil { - return err - } - - pb, err := br.toInfluxDB() - if err != nil { - return err - } - *b = *pb - return nil -} - -// UpdateBucket updates a single bucket with changeset. -// Returns the new bucket state after update. -func (s *BucketService) UpdateBucket(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - var br bucketResponse - err := s.Client. - PatchJSON(newBucketUpdate(&upd), bucketIDPath(id)). - DecodeJSON(&br). - Do(ctx) - if err != nil { - return nil, err - } - return br.toInfluxDB() -} - -// DeleteBucket removes a bucket by ID. -func (s *BucketService) DeleteBucket(ctx context.Context, id influxdb.ID) error { - return s.Client. - Delete(bucketIDPath(id)). - Do(ctx) -} - -// validBucketName reports any errors with bucket names -func validBucketName(bucket *influxdb.Bucket) error { - // names starting with an underscore are reserved for system buckets - if strings.HasPrefix(bucket.Name, "_") && bucket.Type != influxdb.BucketTypeSystem { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Op: "http/bucket", - Msg: fmt.Sprintf("bucket name %s is invalid. Buckets may not start with underscore", bucket.Name), - } - } - return nil -} diff --git a/http/bucket_service_test.go b/http/bucket_service_test.go deleted file mode 100644 index 040dc1b654..0000000000 --- a/http/bucket_service_test.go +++ /dev/null @@ -1,1260 +0,0 @@ -package http - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2" - kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/mock" - "github.com/influxdata/influxdb/v2/pkg/httpc" - platformtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap" - "go.uber.org/zap/zaptest" -) - -// NewMockBucketBackend returns a BucketBackend with mock services. -func NewMockBucketBackend(t *testing.T) *BucketBackend { - return &BucketBackend{ - log: zaptest.NewLogger(t).With(zap.String("handler", "bucket")), - - BucketService: mock.NewBucketService(), - BucketOperationLogService: mock.NewBucketOperationLogService(), - UserResourceMappingService: mock.NewUserResourceMappingService(), - LabelService: mock.NewLabelService(), - UserService: mock.NewUserService(), - OrganizationService: mock.NewOrganizationService(), - } -} - -func TestService_handleGetBuckets(t *testing.T) { - type fields struct { - BucketService influxdb.BucketService - LabelService influxdb.LabelService - } - type args struct { - queryParams map[string][]string - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "get all buckets", - fields: fields{ - &mock.BucketService{ - FindBucketsFn: func(ctx context.Context, filter influxdb.BucketFilter, opts ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { - return []*influxdb.Bucket{ - { - ID: platformtesting.MustIDBase16("0b501e7e557ab1ed"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("50f7ba1150f7ba11"), - RetentionPeriod: 2 * time.Second, - }, - { - ID: platformtesting.MustIDBase16("c0175f0077a77005"), - Name: "example", - OrgID: platformtesting.MustIDBase16("7e55e118dbabb1ed"), - RetentionPeriod: 24 * time.Hour, - }, - }, 2, nil - }, - }, - &mock.LabelService{ - FindResourceLabelsFn: func(ctx context.Context, f influxdb.LabelMappingFilter) ([]*influxdb.Label, error) { - labels := []*influxdb.Label{ - { - ID: platformtesting.MustIDBase16("fc3dc670a4be9b9a"), - Name: "label", - Properties: map[string]string{ - "color": "fff000", - }, - }, - } - return labels, nil - }, - }, - }, - args: args{ - map[string][]string{ - "limit": {"1"}, - }, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "self": "/api/v2/buckets?descending=false&limit=1&offset=0", - "next": "/api/v2/buckets?descending=false&limit=1&offset=1" - }, - "buckets": [ - { - "links": { - "org": "/api/v2/orgs/50f7ba1150f7ba11", - "self": "/api/v2/buckets/0b501e7e557ab1ed", - "logs": "/api/v2/buckets/0b501e7e557ab1ed/logs", - "labels": "/api/v2/buckets/0b501e7e557ab1ed/labels", - "owners": "/api/v2/buckets/0b501e7e557ab1ed/owners", - "members": "/api/v2/buckets/0b501e7e557ab1ed/members", - "write": "/api/v2/write?org=50f7ba1150f7ba11&bucket=0b501e7e557ab1ed" - }, - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "id": "0b501e7e557ab1ed", - "orgID": "50f7ba1150f7ba11", - "type": "user", - "name": "hello", - "retentionRules": [{"type": "expire", "everySeconds": 2}], - "labels": [ - { - "id": "fc3dc670a4be9b9a", - "name": "label", - "properties": { - "color": "fff000" - } - } - ] - }, - { - "links": { - "org": "/api/v2/orgs/7e55e118dbabb1ed", - "self": "/api/v2/buckets/c0175f0077a77005", - "logs": "/api/v2/buckets/c0175f0077a77005/logs", - "labels": "/api/v2/buckets/c0175f0077a77005/labels", - "members": "/api/v2/buckets/c0175f0077a77005/members", - "owners": "/api/v2/buckets/c0175f0077a77005/owners", - "write": "/api/v2/write?org=7e55e118dbabb1ed&bucket=c0175f0077a77005" - }, - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "id": "c0175f0077a77005", - "orgID": "7e55e118dbabb1ed", - "type": "user", - "name": "example", - "retentionRules": [{"type": "expire", "everySeconds": 86400}], - "labels": [ - { - "id": "fc3dc670a4be9b9a", - "name": "label", - "properties": { - "color": "fff000" - } - } - ] - } - ] -} -`, - }, - }, - { - name: "get all buckets when there are none", - fields: fields{ - &mock.BucketService{ - FindBucketsFn: func(ctx context.Context, filter influxdb.BucketFilter, opts ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { - return []*influxdb.Bucket{}, 0, nil - }, - }, - &mock.LabelService{}, - }, - args: args{ - map[string][]string{ - "limit": {"1"}, - }, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "self": "/api/v2/buckets?descending=false&limit=1&offset=0" - }, - "buckets": [] -}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bucketBackend := NewMockBucketBackend(t) - bucketBackend.BucketService = tt.fields.BucketService - bucketBackend.LabelService = tt.fields.LabelService - h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - - r := httptest.NewRequest("GET", "http://any.url", nil) - - qp := r.URL.Query() - for k, vs := range tt.args.queryParams { - for _, v := range vs { - qp.Add(k, v) - } - } - r.URL.RawQuery = qp.Encode() - - w := httptest.NewRecorder() - - h.handleGetBuckets(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handleGetBuckets() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handleGetBuckets() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil || tt.wants.body != "" && !eq { - t.Errorf("%q. handleGetBuckets() = ***%v***", tt.name, diff) - } - }) - } -} - -func TestService_handleGetBucket(t *testing.T) { - type fields struct { - BucketService influxdb.BucketService - } - type args struct { - id string - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "get a bucket by id", - fields: fields{ - &mock.BucketService{ - FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - return &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - RetentionPeriod: 30 * time.Second, - }, nil - } - - return nil, fmt.Errorf("not found") - }, - }, - }, - args: args{ - id: "020f755c3c082000", - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` - { - "links": { - "org": "/api/v2/orgs/020f755c3c082000", - "self": "/api/v2/buckets/020f755c3c082000", - "logs": "/api/v2/buckets/020f755c3c082000/logs", - "labels": "/api/v2/buckets/020f755c3c082000/labels", - "members": "/api/v2/buckets/020f755c3c082000/members", - "owners": "/api/v2/buckets/020f755c3c082000/owners", - "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" - }, - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "id": "020f755c3c082000", - "orgID": "020f755c3c082000", - "type": "user", - "name": "hello", - "retentionRules": [{"type": "expire", "everySeconds": 30}], - "labels": [] - } - `, - }, - }, - { - name: "not found", - fields: fields{ - &mock.BucketService{ - FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - }, - }, - }, - args: args{ - id: "020f755c3c082000", - }, - wants: wants{ - statusCode: http.StatusNotFound, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bucketBackend := NewMockBucketBackend(t) - bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - bucketBackend.BucketService = tt.fields.BucketService - h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - - r := httptest.NewRequest("GET", "http://any.url", nil) - - r = r.WithContext(context.WithValue( - context.Background(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.args.id, - }, - })) - - w := httptest.NewRecorder() - - h.handleGetBucket(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - t.Logf(res.Header.Get("X-Influx-Error")) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handleGetBucket() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handleGetBucket() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handleGetBucket(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handleGetBucket() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handlePostBucket(t *testing.T) { - type fields struct { - BucketService influxdb.BucketService - OrganizationService influxdb.OrganizationService - } - type args struct { - bucket *influxdb.Bucket - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "create a new bucket", - fields: fields{ - BucketService: &mock.BucketService{ - CreateBucketFn: func(ctx context.Context, c *influxdb.Bucket) error { - c.ID = platformtesting.MustIDBase16("020f755c3c082000") - return nil - }, - }, - OrganizationService: &mock.OrganizationService{ - FindOrganizationF: func(ctx context.Context, f influxdb.OrganizationFilter) (*influxdb.Organization, error) { - return &influxdb.Organization{ID: platformtesting.MustIDBase16("6f626f7274697320")}, nil - }, - }, - }, - args: args{ - bucket: &influxdb.Bucket{ - Name: "hello", - OrgID: platformtesting.MustIDBase16("6f626f7274697320"), - }, - }, - wants: wants{ - statusCode: http.StatusCreated, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "org": "/api/v2/orgs/6f626f7274697320", - "self": "/api/v2/buckets/020f755c3c082000", - "logs": "/api/v2/buckets/020f755c3c082000/logs", - "labels": "/api/v2/buckets/020f755c3c082000/labels", - "members": "/api/v2/buckets/020f755c3c082000/members", - "owners": "/api/v2/buckets/020f755c3c082000/owners", - "write": "/api/v2/write?org=6f626f7274697320&bucket=020f755c3c082000" - }, - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "id": "020f755c3c082000", - "orgID": "6f626f7274697320", - "type": "user", - "name": "hello", - "retentionRules": [], - "labels": [] -} -`, - }, - }, - { - name: "create a new bucket with invalid name", - fields: fields{ - BucketService: &mock.BucketService{ - CreateBucketFn: func(ctx context.Context, c *influxdb.Bucket) error { - c.ID = platformtesting.MustIDBase16("020f755c3c082000") - return nil - }, - }, - OrganizationService: &mock.OrganizationService{ - FindOrganizationF: func(ctx context.Context, f influxdb.OrganizationFilter) (*influxdb.Organization, error) { - return &influxdb.Organization{ID: platformtesting.MustIDBase16("6f626f7274697320")}, nil - }, - }, - }, - args: args{ - bucket: &influxdb.Bucket{ - Name: "_hello", - OrgID: platformtesting.MustIDBase16("6f626f7274697320"), - }, - }, - wants: wants{ - statusCode: http.StatusBadRequest, - }, - }, - { - name: "create a new bucket without orgId", - fields: fields{ - BucketService: &mock.BucketService{ - CreateBucketFn: func(ctx context.Context, c *influxdb.Bucket) error { - c.ID = platformtesting.MustIDBase16("020f755c3c082000") - return nil - }, - }, - OrganizationService: &mock.OrganizationService{ - FindOrganizationF: func(ctx context.Context, f influxdb.OrganizationFilter) (*influxdb.Organization, error) { - return &influxdb.Organization{ID: platformtesting.MustIDBase16("6f626f7274697320")}, nil - }, - }, - }, - args: args{ - bucket: &influxdb.Bucket{ - Name: "hello", - }, - }, - wants: wants{ - statusCode: http.StatusBadRequest, - body: `{ - "code": "invalid", - "message": "organization id must be provided" -}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bucketBackend := NewMockBucketBackend(t) - bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - bucketBackend.BucketService = tt.fields.BucketService - bucketBackend.OrganizationService = tt.fields.OrganizationService - h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - - b, err := json.Marshal(newBucket(tt.args.bucket)) - if err != nil { - t.Fatalf("failed to unmarshal bucket: %v", err) - } - - r := httptest.NewRequest("GET", "http://any.url?org=30", bytes.NewReader(b)) - w := httptest.NewRecorder() - - h.handlePostBucket(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handlePostBucket() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handlePostBucket() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handlePostBucket(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handlePostBucket() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handleDeleteBucket(t *testing.T) { - type fields struct { - BucketService influxdb.BucketService - } - type args struct { - id string - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "remove a bucket by id", - fields: fields{ - &mock.BucketService{ - DeleteBucketFn: func(ctx context.Context, id influxdb.ID) error { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - return nil - } - - return fmt.Errorf("wrong id") - }, - }, - }, - args: args{ - id: "020f755c3c082000", - }, - wants: wants{ - statusCode: http.StatusNoContent, - }, - }, - { - name: "bucket not found", - fields: fields{ - &mock.BucketService{ - DeleteBucketFn: func(ctx context.Context, id influxdb.ID) error { - return &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - }, - }, - }, - args: args{ - id: "020f755c3c082000", - }, - wants: wants{ - statusCode: http.StatusNotFound, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bucketBackend := NewMockBucketBackend(t) - bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - bucketBackend.BucketService = tt.fields.BucketService - h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - - r := httptest.NewRequest("GET", "http://any.url", nil) - - r = r.WithContext(context.WithValue( - context.Background(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.args.id, - }, - })) - - w := httptest.NewRecorder() - - h.handleDeleteBucket(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handleDeleteBucket() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handleDeleteBucket() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handleDeleteBucket(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handleDeleteBucket() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handlePatchBucket(t *testing.T) { - type fields struct { - BucketService influxdb.BucketService - } - type args struct { - id string - name string - retention time.Duration - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "update a bucket name and retention", - fields: fields{ - &mock.BucketService{ - FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - return &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - }, nil - }, - UpdateBucketFn: func(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - d := &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - } - - if upd.Name != nil { - d.Name = *upd.Name - } - - if upd.RetentionPeriod != nil { - d.RetentionPeriod = *upd.RetentionPeriod - } - - return d, nil - } - - return nil, fmt.Errorf("not found") - }, - }, - }, - args: args{ - id: "020f755c3c082000", - name: "example", - retention: 2 * time.Second, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "org": "/api/v2/orgs/020f755c3c082000", - "self": "/api/v2/buckets/020f755c3c082000", - "logs": "/api/v2/buckets/020f755c3c082000/logs", - "labels": "/api/v2/buckets/020f755c3c082000/labels", - "members": "/api/v2/buckets/020f755c3c082000/members", - "owners": "/api/v2/buckets/020f755c3c082000/owners", - "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" - }, - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "id": "020f755c3c082000", - "orgID": "020f755c3c082000", - "type": "user", - "name": "example", - "retentionRules": [{"type": "expire", "everySeconds": 2}], - "labels": [] -} -`, - }, - }, - { - name: "update a bucket name invalid", - fields: fields{ - &mock.BucketService{ - FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - return &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - }, nil - } - return nil, fmt.Errorf("not found") - }, - UpdateBucketFn: func(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - return &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - }, nil - } - return nil, fmt.Errorf("not found") - }, - }, - }, - args: args{ - id: "020f755c3c082000", - name: "_example", - }, - wants: wants{ - statusCode: http.StatusBadRequest, - }, - }, - { - name: "bucket not found", - fields: fields{ - &mock.BucketService{ - FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - }, - UpdateBucketFn: func(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - }, - }, - }, - args: args{ - id: "020f755c3c082000", - name: "hello", - retention: time.Second, - }, - wants: wants{ - statusCode: http.StatusNotFound, - }, - }, - { - name: "update bucket to no retention and new name", - fields: fields{ - &mock.BucketService{ - FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - return &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - }, nil - } - return nil, fmt.Errorf("not found") - }, - UpdateBucketFn: func(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - d := &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - } - - if upd.Name != nil { - d.Name = *upd.Name - } - - if upd.RetentionPeriod != nil { - d.RetentionPeriod = *upd.RetentionPeriod - } - - return d, nil - } - - return nil, fmt.Errorf("not found") - }, - }, - }, - args: args{ - id: "020f755c3c082000", - name: "bucket with no retention", - retention: 0, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "org": "/api/v2/orgs/020f755c3c082000", - "self": "/api/v2/buckets/020f755c3c082000", - "logs": "/api/v2/buckets/020f755c3c082000/logs", - "labels": "/api/v2/buckets/020f755c3c082000/labels", - "members": "/api/v2/buckets/020f755c3c082000/members", - "owners": "/api/v2/buckets/020f755c3c082000/owners", - "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" - }, - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "id": "020f755c3c082000", - "orgID": "020f755c3c082000", - "type": "user", - "name": "bucket with no retention", - "retentionRules": [], - "labels": [] -} -`, - }, - }, - { - name: "update retention policy to 'nothing'", - fields: fields{ - &mock.BucketService{ - UpdateBucketFn: func(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - d := &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "b1", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - } - - if upd.Name != nil { - d.Name = *upd.Name - } - - if upd.RetentionPeriod != nil { - d.RetentionPeriod = *upd.RetentionPeriod - } - - return d, nil - } - - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - }, - }, - }, - args: args{ - id: "020f755c3c082000", - retention: 0, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "org": "/api/v2/orgs/020f755c3c082000", - "self": "/api/v2/buckets/020f755c3c082000", - "logs": "/api/v2/buckets/020f755c3c082000/logs", - "labels": "/api/v2/buckets/020f755c3c082000/labels", - "members": "/api/v2/buckets/020f755c3c082000/members", - "owners": "/api/v2/buckets/020f755c3c082000/owners", - "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" - }, - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "id": "020f755c3c082000", - "orgID": "020f755c3c082000", - "type": "user", - "name": "b1", - "retentionRules": [], - "labels": [] -} -`, - }, - }, - { - name: "update a bucket name with invalid retention policy is an error", - fields: fields{ - &mock.BucketService{ - FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - return &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - }, nil - }, - UpdateBucketFn: func(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - if id == platformtesting.MustIDBase16("020f755c3c082000") { - d := &influxdb.Bucket{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), - Name: "hello", - OrgID: platformtesting.MustIDBase16("020f755c3c082000"), - } - - if upd.Name != nil { - d.Name = *upd.Name - } - - if upd.RetentionPeriod != nil { - d.RetentionPeriod = *upd.RetentionPeriod - } - - return d, nil - } - - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - }, - }, - }, - args: args{ - id: "020f755c3c082000", - name: "example", - retention: -10, - }, - wants: wants{ - statusCode: http.StatusUnprocessableEntity, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bucketBackend := NewMockBucketBackend(t) - bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - bucketBackend.BucketService = tt.fields.BucketService - h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - - upd := influxdb.BucketUpdate{} - if tt.args.name != "" { - upd.Name = &tt.args.name - } - - if tt.args.retention != 0 { - upd.RetentionPeriod = &tt.args.retention - } - - b, err := json.Marshal(newBucketUpdate(&upd)) - if err != nil { - t.Fatalf("failed to unmarshal bucket update: %v", err) - } - - r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b)) - - r = r.WithContext(context.WithValue( - context.Background(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.args.id, - }, - })) - - w := httptest.NewRecorder() - - h.handlePatchBucket(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handlePatchBucket() = %v, want %v %v", tt.name, res.StatusCode, tt.wants.statusCode, w.Header()) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handlePatchBucket() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handlePatchBucket(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handlePatchBucket() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handlePostBucketMember(t *testing.T) { - type fields struct { - UserService influxdb.UserService - } - type args struct { - bucketID string - user *influxdb.User - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "add a bucket member", - fields: fields{ - UserService: &mock.UserService{ - FindUserByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.User, error) { - return &influxdb.User{ - ID: id, - Name: "name", - Status: influxdb.Active, - }, nil - }, - }, - }, - args: args{ - bucketID: "020f755c3c082000", - user: &influxdb.User{ - ID: platformtesting.MustIDBase16("6f626f7274697320"), - }, - }, - wants: wants{ - statusCode: http.StatusCreated, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "logs": "/api/v2/users/6f626f7274697320/logs", - "self": "/api/v2/users/6f626f7274697320" - }, - "role": "member", - "id": "6f626f7274697320", - "name": "name", - "status": "active" -} -`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bucketBackend := NewMockBucketBackend(t) - bucketBackend.UserService = tt.fields.UserService - h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - - b, err := json.Marshal(tt.args.user) - if err != nil { - t.Fatalf("failed to marshal user: %v", err) - } - - path := fmt.Sprintf("/api/v2/buckets/%s/members", tt.args.bucketID) - r := httptest.NewRequest("POST", path, bytes.NewReader(b)) - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handlePostBucketMember() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handlePostBucketMember() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handlePostBucketMember(). error unmarshalling json %v", tt.name, err) - } else if tt.wants.body != "" && !eq { - t.Errorf("%q. handlePostBucketMember() = ***%s***", tt.name, diff) - } - }) - } -} - -func TestService_handlePostBucketOwner(t *testing.T) { - type fields struct { - UserService influxdb.UserService - } - type args struct { - bucketID string - user *influxdb.User - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "add a bucket owner", - fields: fields{ - UserService: &mock.UserService{ - FindUserByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.User, error) { - return &influxdb.User{ - ID: id, - Name: "name", - Status: influxdb.Active, - }, nil - }, - }, - }, - args: args{ - bucketID: "020f755c3c082000", - user: &influxdb.User{ - ID: platformtesting.MustIDBase16("6f626f7274697320"), - }, - }, - wants: wants{ - statusCode: http.StatusCreated, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "logs": "/api/v2/users/6f626f7274697320/logs", - "self": "/api/v2/users/6f626f7274697320" - }, - "role": "owner", - "id": "6f626f7274697320", - "name": "name", - "status": "active" -} -`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bucketBackend := NewMockBucketBackend(t) - bucketBackend.UserService = tt.fields.UserService - h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - - b, err := json.Marshal(tt.args.user) - if err != nil { - t.Fatalf("failed to marshal user: %v", err) - } - - path := fmt.Sprintf("/api/v2/buckets/%s/owners", tt.args.bucketID) - r := httptest.NewRequest("POST", path, bytes.NewReader(b)) - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handlePostBucketOwner() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handlePostBucketOwner() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handlePostBucketOwner(). error unmarshalling json %v", tt.name, err) - } else if tt.wants.body != "" && !eq { - t.Errorf("%q. handlePostBucketOwner() = ***%s***", tt.name, diff) - } - }) - } -} - -func initBucketService(f platformtesting.BucketFields, t *testing.T) (influxdb.BucketService, string, func()) { - ctx := context.Background() - logger := zaptest.NewLogger(t) - store := NewTestInmemStore(t) - svc := kv.NewService(logger, store) - svc.IDGenerator = f.IDGenerator - svc.OrgIDs = f.OrgIDs - svc.BucketIDs = f.BucketIDs - svc.TimeGenerator = f.TimeGenerator - if f.TimeGenerator == nil { - svc.TimeGenerator = influxdb.RealTimeGenerator{} - } - - for _, o := range f.Organizations { - // PutOrgs no longer creates an ID - // that is what CreateOrganization does - // so we have to generate one - o.ID = svc.OrgIDs.ID() - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate organizations") - } - } - for _, b := range f.Buckets { - // PutBuckets no longer creates an ID - // that is what CreateBucket does - // so we have to generate one - b.ID = svc.BucketIDs.ID() - if err := svc.PutBucket(ctx, b); err != nil { - t.Fatalf("failed to populate buckets") - } - } - - bucketBackend := NewMockBucketBackend(t) - bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - bucketBackend.BucketService = svc - bucketBackend.OrganizationService = svc - handler := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) - server := httptest.NewServer(handler) - client := BucketService{ - Client: mustNewHTTPClient(t, server.URL, ""), - } - done := server.Close - - return &client, "", done -} - -func TestBucketService(t *testing.T) { - platformtesting.BucketService(initBucketService, t) -} - -func mustNewHTTPClient(t *testing.T, addr, token string) *httpc.Client { - t.Helper() - - httpClient, err := NewHTTPClient(addr, token, false) - if err != nil { - t.Fatal(err) - } - return httpClient -} diff --git a/http/client.go b/http/client.go index 15e3a42e34..ffdc51973a 100644 --- a/http/client.go +++ b/http/client.go @@ -42,22 +42,15 @@ type Service struct { Token string InsecureSkipVerify bool - *AuthorizationService *BackupService - *BucketService *TaskService - *OrganizationService *NotificationRuleService - *UserService *VariableService *WriteService - DocumentService *CheckService *NotificationEndpointService - *UserResourceMappingService *TelegrafService *LabelService - *SecretService DBRPMappingServiceV2 *dbrp.Client } @@ -75,30 +68,23 @@ type Service struct { // in the behavior of the returned service. func NewService(httpClient *httpc.Client, addr, token string) (*Service, error) { return &Service{ - Addr: addr, - Token: token, - AuthorizationService: &AuthorizationService{Client: httpClient}, + Addr: addr, + Token: token, BackupService: &BackupService{ Addr: addr, Token: token, }, - BucketService: &BucketService{Client: httpClient}, TaskService: &TaskService{Client: httpClient}, - OrganizationService: &OrganizationService{Client: httpClient}, NotificationRuleService: &NotificationRuleService{Client: httpClient}, - UserService: &UserService{Client: httpClient}, VariableService: &VariableService{Client: httpClient}, WriteService: &WriteService{ Addr: addr, Token: token, }, - DocumentService: NewDocumentService(httpClient), CheckService: &CheckService{Client: httpClient}, NotificationEndpointService: &NotificationEndpointService{Client: httpClient}, - UserResourceMappingService: &UserResourceMappingService{Client: httpClient}, TelegrafService: NewTelegrafService(httpClient), LabelService: &LabelService{Client: httpClient}, - SecretService: &SecretService{Client: httpClient}, DBRPMappingServiceV2: dbrp.NewClient(httpClient), }, nil } diff --git a/http/delete_test.go b/http/delete_test.go index 055ed616df..fca803ef42 100644 --- a/http/delete_test.go +++ b/http/delete_test.go @@ -16,6 +16,8 @@ import ( "go.uber.org/zap/zaptest" ) +var user1ID = influxtesting.MustIDBase16("020f755c3c082001") + // NewMockDeleteBackend returns a DeleteBackend with mock services. func NewMockDeleteBackend(t *testing.T) *DeleteBackend { return &DeleteBackend{ diff --git a/http/document_service.go b/http/document_service.go deleted file mode 100644 index 6384ce7102..0000000000 --- a/http/document_service.go +++ /dev/null @@ -1,758 +0,0 @@ -package http - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "path" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kit/tracing" - "github.com/influxdata/influxdb/v2/pkg/httpc" - "go.uber.org/zap" -) - -const prefixDocuments = "/api/v2/documents" - -// DocumentService is an interface HTTP-exposed portion of the document service. -type DocumentService interface { - CreateDocument(ctx context.Context, namespace string, orgID influxdb.ID, d *influxdb.Document) error - GetDocuments(ctx context.Context, namespace string, orgID influxdb.ID) ([]*influxdb.Document, error) - GetDocument(ctx context.Context, namespace string, id influxdb.ID) (*influxdb.Document, error) - UpdateDocument(ctx context.Context, namespace string, d *influxdb.Document) error - DeleteDocument(ctx context.Context, namespace string, id influxdb.ID) error - - GetDocumentLabels(ctx context.Context, namespace string, id influxdb.ID) ([]*influxdb.Label, error) - AddDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) (*influxdb.Label, error) - DeleteDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) error -} - -// DocumentBackend is all services and associated parameters required to construct -// the DocumentHandler. -type DocumentBackend struct { - log *zap.Logger - influxdb.HTTPErrorHandler - - DocumentService influxdb.DocumentService - LabelService influxdb.LabelService - OrganizationService influxdb.OrganizationService -} - -// NewDocumentBackend returns a new instance of DocumentBackend. -func NewDocumentBackend(log *zap.Logger, b *APIBackend) *DocumentBackend { - return &DocumentBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: log, - DocumentService: b.DocumentService, - LabelService: b.LabelService, - OrganizationService: b.OrganizationService, - } -} - -// DocumentHandler represents an HTTP API handler for documents. -type DocumentHandler struct { - *httprouter.Router - - log *zap.Logger - influxdb.HTTPErrorHandler - - DocumentService influxdb.DocumentService - LabelService influxdb.LabelService - OrganizationService influxdb.OrganizationService -} - -const ( - documentsPath = "/api/v2/documents/:ns" - documentPath = "/api/v2/documents/:ns/:id" - documentLabelsPath = "/api/v2/documents/:ns/:id/labels" - documentLabelsIDPath = "/api/v2/documents/:ns/:id/labels/:lid" -) - -// NewDocumentHandler returns a new instance of DocumentHandler. -// TODO(desa): this should probably take a namespace -func NewDocumentHandler(b *DocumentBackend) *DocumentHandler { - h := &DocumentHandler{ - Router: NewRouter(b.HTTPErrorHandler), - HTTPErrorHandler: b.HTTPErrorHandler, - log: b.log, - - DocumentService: b.DocumentService, - LabelService: b.LabelService, - OrganizationService: b.OrganizationService, - } - - h.HandlerFunc("POST", documentsPath, h.handlePostDocument) - h.HandlerFunc("GET", documentsPath, h.handleGetDocuments) - h.HandlerFunc("GET", documentPath, h.handleGetDocument) - h.HandlerFunc("PUT", documentPath, h.handlePutDocument) - h.HandlerFunc("DELETE", documentPath, h.handleDeleteDocument) - - h.HandlerFunc("GET", documentLabelsPath, h.handleGetDocumentLabel) - h.HandlerFunc("POST", documentLabelsPath, h.handlePostDocumentLabel) - h.HandlerFunc("DELETE", documentLabelsIDPath, h.handleDeleteDocumentLabel) - - return h -} - -func (h *DocumentHandler) getOrgID(ctx context.Context, org string, orgID influxdb.ID) (influxdb.ID, error) { - if org != "" && orgID.Valid() { - return 0, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "Please provide either org or orgID, not both", - } - } - if orgID.Valid() { - return orgID, nil - } - if org != "" { - o, err := h.OrganizationService.FindOrganization(ctx, influxdb.OrganizationFilter{Name: &org}) - if err != nil { - return 0, err - } - return o.ID, nil - } - return 0, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "Please provide either org or orgID", - } -} - -type documentResponse struct { - Links map[string]string `json:"links"` - *influxdb.Document -} - -func newDocumentResponse(ns string, d *influxdb.Document) *documentResponse { - if d.Labels == nil { - d.Labels = []*influxdb.Label{} - } - return &documentResponse{ - Links: map[string]string{ - "self": fmt.Sprintf("/api/v2/documents/%s/%s", ns, d.ID), - }, - Document: d, - } -} - -type documentsResponse struct { - Documents []*documentResponse `json:"documents"` -} - -func newDocumentsResponse(ns string, docs []*influxdb.Document) *documentsResponse { - ds := make([]*documentResponse, 0, len(docs)) - for _, doc := range docs { - ds = append(ds, newDocumentResponse(ns, doc)) - } - - return &documentsResponse{ - Documents: ds, - } -} - -// handlePostDocument is the HTTP handler for the POST /api/v2/documents/:ns route. -func (h *DocumentHandler) handlePostDocument(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - req, err := decodePostDocumentRequest(ctx, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - orgID, err := h.getOrgID(ctx, req.Org, req.OrgID) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - req.Document.Organizations = make(map[influxdb.ID]influxdb.UserType) - req.Document.Organizations[orgID] = influxdb.Owner - - d := req.Document - for _, lid := range req.Labels { - d.Labels = append(d.Labels, &influxdb.Label{ID: lid}) - } - if err := s.CreateDocument(ctx, d); err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - h.log.Debug("Document created", zap.String("document", fmt.Sprint(req.Document))) - - if err := encodeResponse(ctx, w, http.StatusCreated, newDocumentResponse(req.Namespace, req.Document)); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -type postDocumentRequest struct { - *influxdb.Document - Namespace string `json:"-"` - Org string `json:"org"` - OrgID influxdb.ID `json:"orgID,omitempty"` - // TODO(affo): Why not accepting fully qualified labels? - // this forces us to convert them back and forth. - Labels []influxdb.ID `json:"labels"` -} - -func decodePostDocumentRequest(ctx context.Context, r *http.Request) (*postDocumentRequest, error) { - req := &postDocumentRequest{} - if err := json.NewDecoder(r.Body).Decode(req); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "document body error", - Err: err, - } - } - - if req.Document == nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "missing document body", - } - } - - params := httprouter.ParamsFromContext(ctx) - req.Namespace = params.ByName("ns") - if req.Namespace == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing namespace", - } - } - - return req, nil -} - -// handleGetDocuments is the HTTP handler for the GET /api/v2/documents/:ns route. -func (h *DocumentHandler) handleGetDocuments(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - req, err := decodeGetDocumentsRequest(ctx, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - orgID, err := h.getOrgID(ctx, req.Org, req.OrgID) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - opts := []influxdb.DocumentFindOptions{influxdb.IncludeLabels, influxdb.WhereOrgID(orgID)} - ds, err := s.FindDocuments(ctx, opts...) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - h.log.Debug("Documents retrieved", zap.String("documents", fmt.Sprint(ds))) - - if err := encodeResponse(ctx, w, http.StatusOK, newDocumentsResponse(req.Namespace, ds)); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -type getDocumentsRequest struct { - Namespace string - Org string - OrgID influxdb.ID -} - -func decodeGetDocumentsRequest(ctx context.Context, r *http.Request) (*getDocumentsRequest, error) { - params := httprouter.ParamsFromContext(ctx) - ns := params.ByName("ns") - if ns == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing namespace", - } - } - - qp := r.URL.Query() - req := &getDocumentsRequest{ - Namespace: ns, - Org: qp.Get("org"), - } - - if oidStr := qp.Get("orgID"); oidStr != "" { - oid, err := influxdb.IDFromString(oidStr) - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "Invalid orgID", - } - } - req.OrgID = *oid - } - return req, nil -} - -func (h *DocumentHandler) handlePostDocumentLabel(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - _, _, err := h.getDocument(w, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - req, err := decodePostLabelMappingRequest(ctx, r, influxdb.DocumentsResourceType) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - if err := req.Mapping.Validate(); err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - if err := h.LabelService.CreateLabelMapping(ctx, &req.Mapping); err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - label, err := h.LabelService.FindLabelByID(ctx, req.Mapping.LabelID) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - h.log.Debug("Document label created", zap.String("label", fmt.Sprint(label))) - - if err := encodeResponse(ctx, w, http.StatusCreated, newLabelResponse(label)); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -// handleDeleteDocumentLabel will first remove the label from the document, -// then remove that label. -func (h *DocumentHandler) handleDeleteDocumentLabel(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - req, err := decodeDeleteLabelMappingRequest(ctx, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - _, _, err = h.getDocument(w, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - _, err = h.LabelService.FindLabelByID(ctx, req.LabelID) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - mapping := &influxdb.LabelMapping{ - LabelID: req.LabelID, - ResourceID: req.ResourceID, - ResourceType: influxdb.DocumentsResourceType, - } - - // remove the label - if err := h.LabelService.DeleteLabelMapping(ctx, mapping); err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - h.log.Debug("Document label deleted", zap.String("mapping", fmt.Sprint(mapping))) - - w.WriteHeader(http.StatusNoContent) -} - -func (h *DocumentHandler) handleGetDocumentLabel(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - d, _, err := h.getDocument(w, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - h.log.Debug("Document label retrieved", zap.String("labels", fmt.Sprint(d.Labels))) - - if err := encodeResponse(ctx, w, http.StatusOK, newLabelsResponse(d.Labels)); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -func (h *DocumentHandler) getDocument(w http.ResponseWriter, r *http.Request) (*influxdb.Document, string, error) { - ctx := r.Context() - - req, err := decodeGetDocumentRequest(ctx, r) - if err != nil { - return nil, "", err - } - s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace) - if err != nil { - return nil, "", err - } - ds, err := s.FindDocument(ctx, req.ID) - if err != nil { - return nil, "", err - } - return ds, req.Namespace, nil -} - -// handleGetDocument is the HTTP handler for the GET /api/v2/documents/:ns/:id route. -func (h *DocumentHandler) handleGetDocument(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - d, namespace, err := h.getDocument(w, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - h.log.Debug("Document retrieved", zap.String("document", fmt.Sprint(d))) - - if err := encodeResponse(ctx, w, http.StatusOK, newDocumentResponse(namespace, d)); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -type getDocumentRequest struct { - Namespace string - ID influxdb.ID -} - -func decodeGetDocumentRequest(ctx context.Context, r *http.Request) (*getDocumentRequest, error) { - params := httprouter.ParamsFromContext(ctx) - ns := params.ByName("ns") - if ns == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing namespace", - } - } - - i := params.ByName("id") - if i == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing id", - } - } - - var id influxdb.ID - if err := id.DecodeFromString(i); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "bad id in url", - } - } - - return &getDocumentRequest{ - Namespace: ns, - ID: id, - }, nil -} - -// handleDeleteDocument is the HTTP handler for the DELETE /api/v2/documents/:ns/:id route. -func (h *DocumentHandler) handleDeleteDocument(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - req, err := decodeDeleteDocumentRequest(ctx, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - if err := s.DeleteDocument(ctx, req.ID); err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - h.log.Debug("Document deleted", zap.String("documentID", fmt.Sprint(req.ID))) - - w.WriteHeader(http.StatusNoContent) -} - -type deleteDocumentRequest struct { - Namespace string - ID influxdb.ID -} - -func decodeDeleteDocumentRequest(ctx context.Context, r *http.Request) (*deleteDocumentRequest, error) { - params := httprouter.ParamsFromContext(ctx) - ns := params.ByName("ns") - if ns == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing namespace", - } - } - - i := params.ByName("id") - if i == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing id", - } - } - - var id influxdb.ID - if err := id.DecodeFromString(i); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "bad id in url", - } - } - - return &deleteDocumentRequest{ - Namespace: ns, - ID: id, - }, nil -} - -// handlePutDocument is the HTTP handler for the PUT /api/v2/documents/:ns/:id route. -func (h *DocumentHandler) handlePutDocument(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - req, err := decodePutDocumentRequest(ctx, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - if err := s.UpdateDocument(ctx, req.Document); err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - d, err := s.FindDocument(ctx, req.Document.ID) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - h.log.Debug("Document updated", zap.String("document", fmt.Sprint(d))) - - if err := encodeResponse(ctx, w, http.StatusOK, newDocumentResponse(req.Namespace, d)); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -type putDocumentRequest struct { - *influxdb.Document - Namespace string `json:"-"` -} - -func decodePutDocumentRequest(ctx context.Context, r *http.Request) (*putDocumentRequest, error) { - req := &putDocumentRequest{} - if err := json.NewDecoder(r.Body).Decode(req); err != nil { - return nil, err - } - - params := httprouter.ParamsFromContext(ctx) - i := params.ByName("id") - if i == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing id", - } - } - - if err := req.ID.DecodeFromString(i); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - req.Namespace = params.ByName("ns") - if req.Namespace == "" { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing namespace", - } - } - - return req, nil -} - -type documentService struct { - Client *httpc.Client -} - -// NewDocumentService creates a client to connect to Influx via HTTP to manage documents. -func NewDocumentService(client *httpc.Client) DocumentService { - return &documentService{ - Client: client, - } -} - -func buildDocumentsPath(namespace string) string { - return path.Join(prefixDocuments, namespace) -} -func buildDocumentPath(namespace string, id influxdb.ID) string { - return path.Join(prefixDocuments, namespace, id.String()) -} -func buildDocumentLabelsPath(namespace string, id influxdb.ID) string { - return path.Join(prefixDocuments, namespace, id.String(), "labels") -} -func buildDocumentLabelPath(namespace string, did influxdb.ID, lid influxdb.ID) string { - return path.Join(prefixDocuments, namespace, did.String(), "labels", lid.String()) -} - -// CreateDocument creates a document in the specified namespace. -// Only the ids of the given labels will be used. -// After the call, if successful, the input document will contain the newly assigned ID. -func (s *documentService) CreateDocument(ctx context.Context, namespace string, orgID influxdb.ID, d *influxdb.Document) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - lids := make([]influxdb.ID, len(d.Labels)) - for i := 0; i < len(lids); i++ { - lids[i] = d.Labels[i].ID - } - // Set a valid ID for proper marshaling. - // It will be assigned by the backend in any case. - d.ID = influxdb.ID(1) - req := &postDocumentRequest{ - Document: d, - OrgID: orgID, - Labels: lids, - } - var resp documentResponse - if err := s.Client. - PostJSON(req, buildDocumentsPath(namespace)). - DecodeJSON(&resp). - Do(ctx); err != nil { - return err - } - d.ID = resp.ID - return nil -} - -// GetDocuments returns the documents for a `namespace` and an `orgID`. -// Returned documents do not contain their content. -func (s *documentService) GetDocuments(ctx context.Context, namespace string, orgID influxdb.ID) ([]*influxdb.Document, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var resp documentsResponse - r := s.Client. - Get(buildDocumentsPath(namespace)). - DecodeJSON(&resp) - r = r.QueryParams([2]string{"orgID", orgID.String()}) - if err := r.Do(ctx); err != nil { - return nil, err - } - docs := make([]*influxdb.Document, len(resp.Documents)) - for i := 0; i < len(docs); i++ { - docs[i] = resp.Documents[i].Document - } - return docs, nil -} - -// GetDocument returns the document with the specified id. -func (s *documentService) GetDocument(ctx context.Context, namespace string, id influxdb.ID) (*influxdb.Document, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var resp documentResponse - if err := s.Client. - Get(buildDocumentPath(namespace, id)). - DecodeJSON(&resp). - Do(ctx); err != nil { - return nil, err - } - return resp.Document, nil -} - -// UpdateDocument updates the document with id `d.ID` and replaces the content of `d` with the patched value. -func (s *documentService) UpdateDocument(ctx context.Context, namespace string, d *influxdb.Document) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var resp documentResponse - if err := s.Client. - PutJSON(d, buildDocumentPath(namespace, d.ID)). - DecodeJSON(&resp). - Do(ctx); err != nil { - return err - } - *d = *resp.Document - return nil -} - -// DeleteDocument deletes the document with the given id. -func (s *documentService) DeleteDocument(ctx context.Context, namespace string, id influxdb.ID) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - if err := s.Client. - Delete(buildDocumentPath(namespace, id)). - Do(ctx); err != nil { - return err - } - return nil -} - -// GetDocumentLabels returns the labels associated to the document with the given id. -func (s *documentService) GetDocumentLabels(ctx context.Context, namespace string, id influxdb.ID) ([]*influxdb.Label, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var resp labelsResponse - if err := s.Client. - Get(buildDocumentLabelsPath(namespace, id)). - DecodeJSON(&resp). - Do(ctx); err != nil { - return nil, err - } - return resp.Labels, nil -} - -// AddDocumentLabel adds the label with id `lid` to the document with id `did`. -func (s *documentService) AddDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) (*influxdb.Label, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - mapping := &influxdb.LabelMapping{ - LabelID: lid, - } - var resp labelResponse - if err := s.Client. - PostJSON(mapping, buildDocumentLabelsPath(namespace, did)). - DecodeJSON(&resp). - Do(ctx); err != nil { - return nil, err - } - return &resp.Label, nil -} - -// DeleteDocumentLabel deletes the label with id `lid` from the document with id `did`. -func (s *documentService) DeleteDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - if err := s.Client. - Delete(buildDocumentLabelPath(namespace, did, lid)). - Do(ctx); err != nil { - return err - } - return nil -} diff --git a/http/document_service_test.go b/http/document_service_test.go deleted file mode 100644 index 1cf96133ca..0000000000 --- a/http/document_service_test.go +++ /dev/null @@ -1,760 +0,0 @@ -package http - -import ( - "context" - "net/http/httptest" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/authorizer" - icontext "github.com/influxdata/influxdb/v2/context" - httpmock "github.com/influxdata/influxdb/v2/http/mock" - "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/mock" - itesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -const namespace = "templates" - -type fixture struct { - Org *influxdb.Organization - Labels []*influxdb.Label - Document *influxdb.Document - AnotherDocument *influxdb.Document -} - -func setup(t *testing.T) (func(auth influxdb.Authorizer) *httptest.Server, func(serverUrl string) DocumentService, fixture) { - ctx := context.Background() - // Need this to make resource creation work. - // We are not testing authorization in the setup. - ctx = icontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(true, nil)) - - store := NewTestInmemStore(t) - svc := kv.NewService(zaptest.NewLogger(t), store) - ds, err := svc.CreateDocumentStore(ctx, namespace) - if err != nil { - t.Fatalf("failed to create document store: %v", err) - } - - org := &influxdb.Organization{Name: "org"} - itesting.MustCreateOrgs(ctx, svc, org) - l1 := &influxdb.Label{Name: "l1", OrgID: org.ID} - l2 := &influxdb.Label{Name: "l2", OrgID: org.ID} - l3 := &influxdb.Label{Name: "l3", OrgID: org.ID} - itesting.MustCreateLabels(ctx, svc, l1, l2, l3) - doc := &influxdb.Document{ - Content: "I am a free document", - Labels: []*influxdb.Label{l1, l3}, - Organizations: map[influxdb.ID]influxdb.UserType{org.ID: influxdb.Owner}, - } - if err := ds.CreateDocument(ctx, doc); err != nil { - panic(err) - } - // Organizations are needed only for creation. - // Need to cleanup for comparison later. - doc.Organizations = nil - adoc := &influxdb.Document{ - Content: "I am another document", - Labels: []*influxdb.Label{l1, l2}, - Organizations: map[influxdb.ID]influxdb.UserType{org.ID: influxdb.Owner}, - } - if err := ds.CreateDocument(ctx, adoc); err != nil { - panic(err) - } - - // Organizations are needed only for creation. - // Need to cleanup for comparison later. - adoc.Organizations = nil - backend := NewMockDocumentBackend(t) - backend.HTTPErrorHandler = http.ErrorHandler(0) - backend.DocumentService = authorizer.NewDocumentService(svc) - backend.LabelService = authorizer.NewLabelServiceWithOrg(svc, staticOrgIDResolver(org.ID)) - serverFn := func(auth influxdb.Authorizer) *httptest.Server { - handler := httpmock.NewAuthMiddlewareHandler(NewDocumentHandler(backend), auth) - return httptest.NewServer(handler) - } - clientFn := func(serverUrl string) DocumentService { - return NewDocumentService(mustNewHTTPClient(t, serverUrl, "")) - } - f := fixture{ - Org: org, - Labels: []*influxdb.Label{l1, l2, l3}, - Document: doc, - AnotherDocument: adoc, - } - return serverFn, clientFn, f -} - -func (f fixture) auth(action influxdb.Action) *influxdb.Authorization { - a := &influxdb.Authorization{ - Permissions: []influxdb.Permission{ - { - Action: action, - Resource: influxdb.Resource{ - Type: influxdb.DocumentsResourceType, - OrgID: &f.Org.ID, - }, - }, - }, - Status: influxdb.Active, - } - if action == influxdb.WriteAction { - a.Permissions = append(a.Permissions, influxdb.Permission{ - Action: influxdb.ReadAction, - Resource: influxdb.Resource{ - Type: influxdb.DocumentsResourceType, - OrgID: &f.Org.ID, - }, - }) - } - return a -} - -func (f fixture) authKO() *influxdb.Authorization { - return &influxdb.Authorization{ - Status: influxdb.Active, - } -} - -func (f fixture) addLabelPermission(auth *influxdb.Authorization, action influxdb.Action, lid influxdb.ID) { - ps := []influxdb.Permission{ - { - Action: action, - Resource: influxdb.Resource{ - Type: influxdb.LabelsResourceType, - ID: &lid, - }, - }, - } - if action == influxdb.WriteAction { - ps = append(ps, influxdb.Permission{ - Action: influxdb.ReadAction, - Resource: influxdb.Resource{ - Type: influxdb.LabelsResourceType, - ID: &lid, - }, - }) - } - auth.Permissions = append(auth.Permissions, ps...) -} - -// TestDocumentService tests all the service functions using the document HTTP client. -func TestDocumentService(t *testing.T) { - tests := []struct { - name string - fn func(t *testing.T) - }{ - { - name: "CreateDocument", - fn: CreateDocument, - }, - { - name: "GetDocument", - fn: GetDocument, - }, - { - name: "GetDocuments", - fn: GetDocuments, - }, - { - name: "UpdateDocument", - fn: UpdateDocument, - }, - { - name: "DeleteDocument", - fn: DeleteDocument, - }, - - { - name: "GetLabels", - fn: GetLabels, - }, - { - name: "AddLabels", - fn: AddLabels, - }, - { - name: "DeleteLabel", - fn: DeleteLabel, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.fn(t) - }) - } -} - -func CreateDocument(t *testing.T) { - t.Run("with content", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - orgID := fx.Org.ID - d := &influxdb.Document{ - Content: "I am the content", - } - if err := client.CreateDocument(context.Background(), namespace, orgID, d); err != nil { - t.Fatal(err) - } - if d.ID <= 1 { - t.Errorf("invalid document id: %v", d.ID) - } - if diff := cmp.Diff(d.Content, "I am the content"); diff != "" { - t.Errorf("got unexpected content:\n\t%s", diff) - } - if len(d.Labels) > 0 { - t.Errorf("got unexpected labels: %v", d.Labels) - } - }) - - t.Run("with content and labels", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - orgID := fx.Org.ID - d := &influxdb.Document{ - Content: "I am the content", - Labels: fx.Labels, - } - if err := client.CreateDocument(context.Background(), namespace, orgID, d); err != nil { - t.Fatal(err) - } - if d.ID <= 1 { - t.Errorf("invalid document id: %v", d.ID) - } - if diff := cmp.Diff(d.Content, "I am the content"); diff != "" { - t.Errorf("got unexpected content:\n\t%s", diff) - } - if diff := cmp.Diff(d.Labels, fx.Labels); diff != "" { - t.Errorf("got unexpected labels:\n\t%v", diff) - } - }) - - t.Run("bad label", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - orgID := fx.Org.ID - d := &influxdb.Document{ - Content: "I am the content", - Labels: []*influxdb.Label{ - { - ID: influxdb.ID(1), - Name: "bad", - }, - }, - } - if err := client.CreateDocument(context.Background(), namespace, orgID, d); err != nil { - if !strings.Contains(err.Error(), "label not found") { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) - - t.Run("unauthorized", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.authKO()) - defer server.Close() - client := clientFn(server.URL) - d := &influxdb.Document{ - Content: "I am the content", - } - if err := client.CreateDocument(context.Background(), namespace, fx.Org.ID, d); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) - - t.Run("unauthorized - insufficient", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.ReadAction)) - defer server.Close() - client := clientFn(server.URL) - d := &influxdb.Document{ - Content: "I am the content", - } - if err := client.CreateDocument(context.Background(), namespace, fx.Org.ID, d); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) -} - -func GetDocument(t *testing.T) { - t.Run("existing", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - want := fx.Document - server := serverFn(fx.auth(influxdb.ReadAction)) - defer server.Close() - client := clientFn(server.URL) - got, err := client.GetDocument(context.Background(), namespace, want.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("got unexpected document:\n\t%s", diff) - } - }) - - t.Run("non existing", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - id := fx.Document.ID + 42 - server := serverFn(fx.auth(influxdb.ReadAction)) - defer server.Close() - client := clientFn(server.URL) - if _, err := client.GetDocument(context.Background(), namespace, id); err != nil { - if !strings.Contains(err.Error(), "document not found") { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) - - t.Run("unauthorized", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.authKO()) - defer server.Close() - client := clientFn(server.URL) - if _, err := client.GetDocument(context.Background(), namespace, fx.Document.ID); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) -} - -func GetDocuments(t *testing.T) { - t.Run("get existing documents", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.ReadAction)) - defer server.Close() - client := clientFn(server.URL) - got, err := client.GetDocuments(context.Background(), namespace, fx.Org.ID) - if err != nil { - t.Fatal(err) - } - want := []*influxdb.Document{fx.Document, fx.AnotherDocument} - want[0].Content = nil // response will not contain the content of documents - want[1].Content = nil // response will not contain the content of documents - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("got unexpected document:\n\t%s", diff) - } - }) - - t.Run("unauthorized", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.authKO()) - defer server.Close() - client := clientFn(server.URL) - docs, err := client.GetDocuments(context.Background(), namespace, fx.Org.ID) - if err != nil { - t.Fatal(err) - } - if len(docs) > 0 { - t.Fatalf("no document expected, returned %v instead", docs) - } - }) -} - -func UpdateDocument(t *testing.T) { - t.Run("update content", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - want := fx.Document - want.Content = "new content" - if err := client.UpdateDocument(context.Background(), namespace, want); err != nil { - t.Fatal(err) - } - got, err := client.GetDocument(context.Background(), namespace, want.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("got unexpected document:\n\t%s", diff) - } - }) - - t.Run("update labels", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - want := fx.Document - want.Labels = want.Labels[:0] - if err := client.UpdateDocument(context.Background(), namespace, want); err != nil { - t.Fatal(err) - } - got, err := client.GetDocument(context.Background(), namespace, want.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("got unexpected document:\n\t%s", diff) - } - }) - - t.Run("update meta", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - want := fx.Document - want.Meta.Name = "new name" - if err := client.UpdateDocument(context.Background(), namespace, want); err != nil { - t.Fatal(err) - } - got, err := client.GetDocument(context.Background(), namespace, want.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("got unexpected document:\n\t%s", diff) - } - }) - - t.Run("unauthorized - wrong org", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(func() influxdb.Authorizer { - a := fx.auth(influxdb.WriteAction) - for _, p := range a.Permissions { - *p.Resource.OrgID++ - } - return a - }()) - defer server.Close() - client := clientFn(server.URL) - want := fx.AnotherDocument - want.Content = "new content" - if err := client.UpdateDocument(context.Background(), namespace, want); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) - - t.Run("unauthorized - insufficient permissions", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.authKO()) - defer server.Close() - client := clientFn(server.URL) - want := fx.Document - want.Content = "new content" - if err := client.UpdateDocument(context.Background(), namespace, want); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) -} - -func DeleteDocument(t *testing.T) { - t.Run("existing", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - want := fx.Document - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - pre, err := client.GetDocuments(context.Background(), namespace, fx.Org.ID) - if err != nil { - t.Fatal(err) - } - l := len(pre) - if err := client.DeleteDocument(context.Background(), namespace, want.ID); err != nil { - t.Fatal(err) - } - got, err := client.GetDocuments(context.Background(), namespace, fx.Org.ID) - if err != nil { - t.Fatal(err) - } - lgot := len(got) - lwant := l - 1 - if diff := cmp.Diff(lgot, lwant); diff != "" { - t.Errorf("got unexpected length of docs:\n\t%v", diff) - } - }) - - t.Run("non existing", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - id := fx.Document.ID + 42 - server := serverFn(fx.auth(influxdb.WriteAction)) - defer server.Close() - client := clientFn(server.URL) - if err := client.DeleteDocument(context.Background(), namespace, id); err != nil { - if !strings.Contains(err.Error(), "document not found") { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - got, err := client.GetDocuments(context.Background(), namespace, fx.Org.ID) - if err != nil { - t.Fatal(err) - } - lgot := len(got) - lwant := 2 - if diff := cmp.Diff(lgot, lwant); diff != "" { - t.Errorf("got unexpected length of docs:\n\t%v", diff) - } - }) - - t.Run("unauthorized", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.authKO()) - defer server.Close() - client := clientFn(server.URL) - if err := client.DeleteDocument(context.Background(), namespace, fx.Document.ID); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) - - t.Run("unauthorized - insufficient", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.auth(influxdb.ReadAction)) - defer server.Close() - client := clientFn(server.URL) - if err := client.DeleteDocument(context.Background(), namespace, fx.Document.ID); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) -} - -func GetLabels(t *testing.T) { - t.Run("get labels", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - // GetLabels does not pass through the LabelService. Org permissions are enough. - server := serverFn(fx.auth(influxdb.ReadAction)) - defer server.Close() - client := clientFn(server.URL) - got, err := client.GetDocumentLabels(context.Background(), namespace, fx.Document.ID) - if err != nil { - t.Fatal(err) - } - want := fx.Document.Labels - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("got unexpected labels:\n\t%s", diff) - } - }) - - t.Run("unauthorized", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.authKO()) - defer server.Close() - client := clientFn(server.URL) - if _, err := client.GetDocumentLabels(context.Background(), namespace, fx.AnotherDocument.ID); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) -} - -func AddLabels(t *testing.T) { - t.Run("add one", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(func() influxdb.Authorizer { - a := fx.auth(influxdb.WriteAction) - // The LabelService uses a "standard auth" mode. - // That's why we need to add further permissions and the org ones are not enough. - fx.addLabelPermission(a, influxdb.WriteAction, fx.Labels[1].ID) - return a - }()) - defer server.Close() - client := clientFn(server.URL) - got, err := client.AddDocumentLabel(context.Background(), namespace, fx.Document.ID, fx.Labels[1].ID) - if err != nil { - t.Fatal(err) - } - want := fx.Labels[1] - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("got unexpected labels:\n\t%s", diff) - } - gotLs, err := client.GetDocumentLabels(context.Background(), namespace, fx.Document.ID) - if err != nil { - t.Fatal(err) - } - wantLs := fx.Labels - if diff := cmp.Diff(gotLs, wantLs); diff != "" { - t.Errorf("got unexpected labels:\n\t%s", diff) - } - }) - - t.Run("unauthorized", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(fx.authKO()) - defer server.Close() - client := clientFn(server.URL) - if _, err := client.AddDocumentLabel(context.Background(), namespace, fx.Document.ID, fx.Labels[1].ID); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) - - t.Run("unauthorized - insufficient", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(func() influxdb.Authorizer { - a := fx.auth(influxdb.WriteAction) - fx.addLabelPermission(a, influxdb.ReadAction, fx.Labels[1].ID) - return a - }()) - defer server.Close() - client := clientFn(server.URL) - if _, err := client.AddDocumentLabel(context.Background(), namespace, fx.Document.ID, fx.Labels[1].ID); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - }) - - t.Run("add same twice", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(func() influxdb.Authorizer { - a := fx.auth(influxdb.WriteAction) - // The LabelService uses a "standard auth" mode. - // That's why we need to add further permissions and the org ones are not enough. - fx.addLabelPermission(a, influxdb.WriteAction, fx.Labels[0].ID) - return a - }()) - defer server.Close() - client := clientFn(server.URL) - if _, err := client.AddDocumentLabel(context.Background(), namespace, fx.Document.ID, fx.Labels[0].ID); err != nil { - if !strings.Contains(err.Error(), influxdb.ErrLabelExistsOnResource.Msg) { - t.Errorf("unexpected error: %v", err.Error()) - } - } else { - t.Error("expected error got none") - } - }) -} - -func DeleteLabel(t *testing.T) { - t.Run("existing", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(func() influxdb.Authorizer { - a := fx.auth(influxdb.WriteAction) - // The LabelService uses a "standard auth" mode. - // That's why we need to add further permissions and the org ones are not enough. - fx.addLabelPermission(a, influxdb.WriteAction, fx.Document.Labels[0].ID) - return a - }()) - defer server.Close() - client := clientFn(server.URL) - pre, err := client.GetDocumentLabels(context.Background(), namespace, fx.Document.ID) - if err != nil { - t.Fatal(err) - } - l := len(pre) - if err := client.DeleteDocumentLabel(context.Background(), namespace, fx.Document.ID, fx.Document.Labels[0].ID); err != nil { - t.Fatal(err) - } - got, err := client.GetDocumentLabels(context.Background(), namespace, fx.Document.ID) - if err != nil { - t.Fatal(err) - } - lgot := len(got) - lwant := l - 1 - if diff := cmp.Diff(lgot, lwant); diff != "" { - t.Errorf("got unexpected length of docs:\n\t%v", diff) - } - }) - - t.Run("non existing", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - badLId := fx.Labels[2].ID + 42 - server := serverFn(func() influxdb.Authorizer { - a := fx.auth(influxdb.WriteAction) - fx.addLabelPermission(a, influxdb.WriteAction, badLId) - return a - }()) - defer server.Close() - client := clientFn(server.URL) - if err := client.DeleteDocumentLabel(context.Background(), namespace, fx.Document.ID, badLId); err != nil { - if !strings.Contains(err.Error(), "label not found") { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - got, err := client.GetDocumentLabels(context.Background(), namespace, fx.Document.ID) - if err != nil { - t.Fatal(err) - } - lgot := len(got) - lwant := 2 - if diff := cmp.Diff(lgot, lwant); diff != "" { - t.Errorf("got unexpected length of labels:\n\t%v", diff) - } - }) - - t.Run("unauthorized - insufficient", func(t *testing.T) { - serverFn, clientFn, fx := setup(t) - server := serverFn(func() influxdb.Authorizer { - a := fx.auth(influxdb.WriteAction) - fx.addLabelPermission(a, influxdb.ReadAction, fx.Document.Labels[0].ID) - return a - }()) - defer server.Close() - client := clientFn(server.URL) - if err := client.DeleteDocumentLabel(context.Background(), namespace, fx.Document.ID, fx.Document.Labels[0].ID); err != nil { - if influxdb.ErrorCode(err) != influxdb.EUnauthorized { - t.Errorf("unexpected error: %v", err) - } - } else { - t.Error("expected error got none") - } - got, err := client.GetDocumentLabels(context.Background(), namespace, fx.Document.ID) - if err != nil { - t.Fatal(err) - } - lgot := len(got) - lwant := 2 - if diff := cmp.Diff(lgot, lwant); diff != "" { - t.Errorf("got unexpected length of labels:\n\t%v", diff) - } - }) -} - -type staticOrgIDResolver influxdb.ID - -func (s staticOrgIDResolver) FindResourceOrganizationID(ctx context.Context, rt influxdb.ResourceType, id influxdb.ID) (influxdb.ID, error) { - return (influxdb.ID)(s), nil -} diff --git a/http/document_test.go b/http/document_test.go deleted file mode 100644 index 6284ab110b..0000000000 --- a/http/document_test.go +++ /dev/null @@ -1,960 +0,0 @@ -package http - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2" - pcontext "github.com/influxdata/influxdb/v2/context" - kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/mock" - influxtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -var ( - doc1ID = influxtesting.MustIDBase16("020f755c3c082010") - doc2ID = influxtesting.MustIDBase16("020f755c3c082011") - doc3ID = influxtesting.MustIDBase16("020f755c3c082012") - doc4ID = influxtesting.MustIDBase16("020f755c3c082013") - doc5ID = influxtesting.MustIDBase16("020f755c3c082014") - doc6ID = influxtesting.MustIDBase16("020f755c3c082015") - user1ID = influxtesting.MustIDBase16("020f755c3c082001") - label1ID = influxtesting.MustIDBase16("020f755c3c082300") - label2ID = influxtesting.MustIDBase16("020f755c3c082301") - label3ID = influxtesting.MustIDBase16("020f755c3c082302") - label1 = influxdb.Label{ - ID: label1ID, - Name: "l1", - } - label2 = influxdb.Label{ - ID: label2ID, - Name: "l2", - } - label3 = influxdb.Label{ - ID: label3ID, - Name: "l3", - } - label3MappingJSON, _ = json.Marshal(influxdb.LabelMapping{ - LabelID: label3ID, - }) - mockGen = mock.TimeGenerator{ - FakeValue: time.Date(2006, 5, 24, 1, 2, 3, 4, time.UTC), - } - doc1 = influxdb.Document{ - ID: doc1ID, - Meta: influxdb.DocumentMeta{ - Name: "doc1", - Type: "typ1", - Description: "desc1", - }, - Content: "content1", - Labels: []*influxdb.Label{ - &label1, - }, - } - doc2 = influxdb.Document{ - ID: doc2ID, - Meta: influxdb.DocumentMeta{ - Name: "doc2", - }, - Content: "content2", - Labels: []*influxdb.Label{}, - } - doc3 = influxdb.Document{ - ID: doc3ID, - Meta: influxdb.DocumentMeta{ - Name: "doc3", - }, - Content: "content3", - Labels: []*influxdb.Label{ - &label2, - }, - } - doc4 = influxdb.Document{ - ID: doc4ID, - Meta: influxdb.DocumentMeta{ - Name: "doc4", - Type: "typ4", - }, - Content: "content4", - } - doc5 = influxdb.Document{ - ID: doc5ID, - Meta: influxdb.DocumentMeta{ - Name: "doc5", - }, - Content: "content5", - } - doc6 = influxdb.Document{ - ID: doc6ID, - Meta: influxdb.DocumentMeta{ - Name: "doc6", - }, - Content: "content6", - } - - docs = []*influxdb.Document{ - &doc1, - &doc2, - } - docsResp = `{ - "documents":[ - { - "id": "020f755c3c082010", - "links": { - "self": "/api/v2/documents/template/020f755c3c082010" - }, - "content": "content1", - "labels": [ - { - "id": "020f755c3c082300", - "name": "l1" - } - ], - "meta": { - "name": "doc1", - "type": "typ1", - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z", - "description": "desc1" - } - }, - { - "id": "020f755c3c082011", - "links": { - "self": "/api/v2/documents/template/020f755c3c082011" - }, - "content": "content2", - "meta": { - "name": "doc2", - "createdAt": "0001-01-01T00:00:00Z", - "updatedAt": "0001-01-01T00:00:00Z" - } - } - ] - }` - findDocsServiceMock = &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - FindDocumentsFn: func(ctx context.Context, opts ...influxdb.DocumentFindOptions) ([]*influxdb.Document, error) { - return docs, nil - }, - }, nil - }, - } - findDoc1ServiceMock = &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - FindDocumentFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Document, error) { - return &doc1, nil - }, - }, nil - }, - } - findDoc2ServiceMock = &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - FindDocumentFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Document, error) { - return &doc2, nil - }, - }, nil - }, - } - getOrgIDServiceMock = &mock.OrganizationService{ - FindOrganizationF: func(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) { - return &influxdb.Organization{ - ID: 1, - }, nil - }, - } -) - -// NewMockDocumentBackend returns a DocumentBackend with mock services. -func NewMockDocumentBackend(t *testing.T) *DocumentBackend { - return &DocumentBackend{ - log: zaptest.NewLogger(t), - - DocumentService: mock.NewDocumentService(), - LabelService: mock.NewLabelService(), - } -} - -func TestService_handleDeleteDocumentLabel(t *testing.T) { - type fields struct { - DocumentService influxdb.DocumentService - LabelService influxdb.LabelService - } - type args struct { - authorizer influxdb.Authorizer - documentID influxdb.ID - labelID influxdb.ID - } - type wants struct { - statusCode int - contentType string - body string - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "bad doc id", - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid", "message":"url missing resource id"}`, - }, - }, - { - name: "bad label id", - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc1ID, - }, - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid", "message":"label id is missing"}`, - }, - }, - { - name: "label not found", - fields: fields{ - DocumentService: findDoc2ServiceMock, - LabelService: &mock.LabelService{ - FindLabelByIDFn: func(context.Context, influxdb.ID) (*influxdb.Label, error) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "label not found", - } - }, - }, - }, - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc2ID, - labelID: label1ID, - }, - wants: wants{ - statusCode: http.StatusNotFound, - contentType: "application/json; charset=utf-8", - body: `{"code":"not found", "message":"label not found"}`, - }, - }, - { - name: "regular get labels", - fields: fields{ - DocumentService: &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - FindDocumentFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Document, error) { - return &doc3, nil - }, - UpdateDocumentFn: func(ctx context.Context, d *influxdb.Document) error { - return nil - }, - }, nil - }, - }, - LabelService: &mock.LabelService{ - FindLabelByIDFn: func(context.Context, influxdb.ID) (*influxdb.Label, error) { - return &label2, nil - }, - DeleteLabelMappingFn: func(context.Context, *influxdb.LabelMapping) error { - return nil - }, - }, - }, - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc3ID, - labelID: label2ID, - }, - wants: wants{ - statusCode: http.StatusNoContent, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - documentBackend := NewMockDocumentBackend(t) - documentBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - documentBackend.DocumentService = tt.fields.DocumentService - documentBackend.LabelService = tt.fields.LabelService - h := NewDocumentHandler(documentBackend) - r := httptest.NewRequest("DELETE", "http://any.url", nil) - r = r.WithContext(pcontext.SetAuthorizer(r.Context(), tt.args.authorizer)) - r = r.WithContext(context.WithValue(r.Context(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "ns", - Value: "template", - }, - { - Key: "id", - Value: tt.args.documentID.String(), - }, - { - Key: "lid", - Value: tt.args.labelID.String(), - }, - })) - w := httptest.NewRecorder() - h.handleDeleteDocumentLabel(w, r) - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handleDeleteDocumentLabel() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handleDeleteDocumentLabel() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handleDeleteDocumentLabel(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handleDeleteDocumentLabel() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handlePostDocumentLabel(t *testing.T) { - type fields struct { - DocumentService influxdb.DocumentService - LabelService influxdb.LabelService - } - type args struct { - body *bytes.Buffer - authorizer influxdb.Authorizer - documentID influxdb.ID - } - type wants struct { - statusCode int - contentType string - body string - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "bad doc id", - args: args{ - body: new(bytes.Buffer), - }, - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid", "message":"url missing id"}`, - }, - }, - { - name: "doc not found", - fields: fields{ - DocumentService: &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - FindDocumentFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Document, error) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "doc not found", - } - }, - }, nil - }, - }, - }, - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc2ID, - body: new(bytes.Buffer), - }, - wants: wants{ - statusCode: http.StatusNotFound, - contentType: "application/json; charset=utf-8", - body: `{"code":"not found", "message":"doc not found"}`, - }, - }, - { - name: "empty post a label", - fields: fields{ - DocumentService: &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - FindDocumentFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Document, error) { - return &doc3, nil - }, - UpdateDocumentFn: func(ctx context.Context, d *influxdb.Document) error { - return nil - }, - }, nil - }, - }, - LabelService: &mock.LabelService{ - FindLabelByIDFn: func(context.Context, influxdb.ID) (*influxdb.Label, error) { - return &label2, nil - }, - DeleteLabelMappingFn: func(context.Context, *influxdb.LabelMapping) error { - return nil - }, - }, - }, - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc3ID, - body: new(bytes.Buffer), - }, - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid", "message":"Invalid post label map request"}`, - }, - }, - { - name: "regular post a label", - fields: fields{ - DocumentService: &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - TimeGenerator: mockGen, - FindDocumentFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Document, error) { - return &doc4, nil - }, - UpdateDocumentFn: func(ctx context.Context, d *influxdb.Document) error { - return nil - }, - }, nil - }, - }, - LabelService: &mock.LabelService{ - CreateLabelMappingFn: func(context.Context, *influxdb.LabelMapping) error { - return nil - }, - FindLabelByIDFn: func(context.Context, influxdb.ID) (*influxdb.Label, error) { - return &label3, nil - }, - DeleteLabelMappingFn: func(context.Context, *influxdb.LabelMapping) error { - return nil - }, - }, - }, - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc3ID, - body: bytes.NewBuffer(label3MappingJSON), - }, - wants: wants{ - statusCode: http.StatusCreated, - contentType: "application/json; charset=utf-8", - body: `{"label": { - "id": "020f755c3c082302", - "name": "l3" - }, - "links": {"self": "/api/v2/labels/020f755c3c082302"}}`, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - documentBackend := NewMockDocumentBackend(t) - documentBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - documentBackend.DocumentService = tt.fields.DocumentService - documentBackend.LabelService = tt.fields.LabelService - h := NewDocumentHandler(documentBackend) - r := httptest.NewRequest("POST", "http://any.url", tt.args.body) - r = r.WithContext(pcontext.SetAuthorizer(r.Context(), tt.args.authorizer)) - r = r.WithContext(context.WithValue(r.Context(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "ns", - Value: "template", - }, - { - Key: "id", - Value: tt.args.documentID.String(), - }, - })) - w := httptest.NewRecorder() - h.handlePostDocumentLabel(w, r) - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handlePostDocumentLabel() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handlePostDocumentLabel() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handlePostDocumentLabel(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handlePostDocumentLabel() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handleGetDocumentLabels(t *testing.T) { - type fields struct { - DocumentService influxdb.DocumentService - LabelService influxdb.LabelService - } - type args struct { - queryParams map[string][]string - authorizer influxdb.Authorizer - documentID influxdb.ID - } - type wants struct { - statusCode int - contentType string - body string - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "invalid document id", - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid", "message":"url missing id"}`, - }, - }, - { - name: "regular get labels", - fields: fields{ - DocumentService: findDoc1ServiceMock, - }, - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc1ID, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: `{"labels": [{ - "id": "020f755c3c082300", - "name": "l1" - }],"links":{"self":"/api/v2/labels"}}`}, - }, - { - name: "find no labels", - fields: fields{ - DocumentService: findDoc2ServiceMock, - }, - args: args{ - authorizer: &influxdb.Session{UserID: user1ID}, - documentID: doc1ID, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: `{"labels": [],"links":{"self":"/api/v2/labels"}}`}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - documentBackend := NewMockDocumentBackend(t) - documentBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - documentBackend.DocumentService = tt.fields.DocumentService - documentBackend.LabelService = tt.fields.LabelService - h := NewDocumentHandler(documentBackend) - r := httptest.NewRequest("GET", "http://any.url", nil) - qp := r.URL.Query() - for k, vs := range tt.args.queryParams { - for _, v := range vs { - qp.Add(k, v) - } - } - r.URL.RawQuery = qp.Encode() - r = r.WithContext(pcontext.SetAuthorizer(r.Context(), tt.args.authorizer)) - r = r.WithContext(context.WithValue(r.Context(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "ns", - Value: "template", - }, - { - Key: "id", - Value: tt.args.documentID.String(), - }, - })) - w := httptest.NewRecorder() - h.handleGetDocumentLabel(w, r) - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handleGetDocumentLabel() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handleGetDocumentLabel() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handleGetDocumentLabel(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handleGetDocumentLabel() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handleGetDocuments(t *testing.T) { - type fields struct { - DocumentService influxdb.DocumentService - OrganizationService influxdb.OrganizationService - } - type args struct { - queryParams map[string][]string - authorizer influxdb.Authorizer - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "get all documents with both org and orgID", - fields: fields{ - DocumentService: findDocsServiceMock, - }, - args: args{ - queryParams: map[string][]string{ - "orgID": []string{"020f755c3c082002"}, - "org": []string{"org1"}, - }, - authorizer: &influxdb.Session{UserID: user1ID}, - }, - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid", "message":"Please provide either org or orgID, not both"}`, - }, - }, - { - name: "get all documents with orgID", - fields: fields{ - DocumentService: findDocsServiceMock, - }, - args: args{ - queryParams: map[string][]string{ - "orgID": []string{"020f755c3c082002"}, - }, - authorizer: &influxdb.Session{UserID: user1ID}, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: docsResp, - }, - }, - { - name: "get all documents with org name", - fields: fields{ - DocumentService: findDocsServiceMock, - OrganizationService: getOrgIDServiceMock, - }, - args: args{ - queryParams: map[string][]string{ - "org": []string{"org1"}, - }, - authorizer: &influxdb.Session{UserID: user1ID}, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: docsResp, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - documentBackend := NewMockDocumentBackend(t) - documentBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - documentBackend.DocumentService = tt.fields.DocumentService - documentBackend.OrganizationService = tt.fields.OrganizationService - h := NewDocumentHandler(documentBackend) - r := httptest.NewRequest("GET", "http://any.url", nil) - qp := r.URL.Query() - for k, vs := range tt.args.queryParams { - for _, v := range vs { - qp.Add(k, v) - } - } - r.URL.RawQuery = qp.Encode() - r = r.WithContext(pcontext.SetAuthorizer(r.Context(), tt.args.authorizer)) - r = r.WithContext(context.WithValue(r.Context(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "ns", - Value: "template", - }})) - w := httptest.NewRecorder() - h.handleGetDocuments(w, r) - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handleGetDocuments() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handleGetDocuments() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handleGetDocuments(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handleGetDocuments() = ***%s***", tt.name, diff) - } - } - }) - } -} - -func TestService_handlePostDocuments(t *testing.T) { - type fields struct { - DocumentService influxdb.DocumentService - LabelService influxdb.LabelService - OrganizationService influxdb.OrganizationService - } - type args struct { - body *bytes.Buffer - queryParams map[string][]string - authorizer influxdb.Authorizer - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "blank body", - fields: fields{ - DocumentService: &mock.DocumentService{}, - LabelService: &mock.LabelService{}, - }, - args: args{ - body: bytes.NewBuffer([]byte{}), - queryParams: map[string][]string{ - "org": []string{"org1"}, - }, - authorizer: &influxdb.Session{UserID: user1ID}, - }, - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid","message": "document body error: EOF"}`, - }, - }, - { - name: "empty json", - fields: fields{ - DocumentService: &mock.DocumentService{}, - LabelService: &mock.LabelService{}, - }, - args: args{ - body: bytes.NewBuffer([]byte(`{}`)), - queryParams: map[string][]string{ - "org": []string{"org1"}, - }, - authorizer: &influxdb.Session{UserID: user1ID}, - }, - wants: wants{ - statusCode: http.StatusBadRequest, - contentType: "application/json; charset=utf-8", - body: `{"code":"invalid","message": "missing document body"}`, - }, - }, - { - name: "without label", - fields: fields{ - DocumentService: &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - TimeGenerator: mockGen, - CreateDocumentFn: func(ctx context.Context, d *influxdb.Document) error { - d.Meta.CreatedAt = mockGen.Now() - return nil - }, - }, nil - }, - }, - LabelService: &mock.LabelService{}, - OrganizationService: getOrgIDServiceMock, - }, - args: args{ - body: func() *bytes.Buffer { - req := postDocumentRequest{ - Document: &doc5, - Org: "org1", - } - m, _ := json.Marshal(req) - return bytes.NewBuffer(m) - }(), - queryParams: map[string][]string{ - "org": []string{"org1"}, - }, - authorizer: &influxdb.Session{UserID: user1ID}, - }, - wants: wants{ - statusCode: http.StatusCreated, - contentType: "application/json; charset=utf-8", - body: `{ - "content": "content5", - "id": "020f755c3c082014", - "links": { - "self": "/api/v2/documents/template/020f755c3c082014" - }, - "meta": { - "name": "doc5", - "createdAt": "2006-05-24T01:02:03.000000004Z", - "updatedAt": "0001-01-01T00:00:00Z" - }}`, - }, - }, - { - name: "with label", - fields: fields{ - DocumentService: &mock.DocumentService{ - FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { - return &mock.DocumentStore{ - TimeGenerator: mockGen, - CreateDocumentFn: func(ctx context.Context, d *influxdb.Document) error { - d.Labels = []*influxdb.Label{&label1, &label2} - d.Meta.CreatedAt = mockGen.Now() - return nil - }, - }, nil - }, - }, - LabelService: &mock.LabelService{ - FindLabelByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Label, error) { - if id == label1ID { - return &label1, nil - } - return &label2, nil - }, - }, - OrganizationService: getOrgIDServiceMock, - }, - args: args{ - body: func() *bytes.Buffer { - req := postDocumentRequest{ - Document: &doc6, - Org: "org1", - } - m, _ := json.Marshal(req) - return bytes.NewBuffer(m) - }(), - authorizer: &influxdb.Session{UserID: user1ID}, - }, - wants: wants{ - statusCode: http.StatusCreated, - contentType: "application/json; charset=utf-8", - body: `{ - "content": "content6", - "id": "020f755c3c082015", - "links": { - "self": "/api/v2/documents/template/020f755c3c082015" - }, - "labels": [{ - "id": "020f755c3c082300", - "name": "l1" - }, - { - "id": "020f755c3c082301", - "name": "l2" - }], - "meta": { - "name": "doc6", - "createdAt": "2006-05-24T01:02:03.000000004Z", - "updatedAt": "0001-01-01T00:00:00Z" - }}`, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - documentBackend := NewMockDocumentBackend(t) - documentBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - documentBackend.DocumentService = tt.fields.DocumentService - documentBackend.LabelService = tt.fields.LabelService - documentBackend.OrganizationService = tt.fields.OrganizationService - h := NewDocumentHandler(documentBackend) - r := httptest.NewRequest("POST", "http://any.url", tt.args.body) - r = r.WithContext(pcontext.SetAuthorizer(r.Context(), tt.args.authorizer)) - r = r.WithContext(context.WithValue(r.Context(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "ns", - Value: "template", - }, - })) - w := httptest.NewRecorder() - h.handlePostDocument(w, r) - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("%q. handlePostDocument() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. handlePostDocument() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(tt.wants.body, string(body)); err != nil { - t.Errorf("%q, handlePostDocument(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handlePostDocument() = ***%s***", tt.name, diff) - } - } - }) - } -} diff --git a/http/helpers.go b/http/helpers.go deleted file mode 100644 index 324f1380b5..0000000000 --- a/http/helpers.go +++ /dev/null @@ -1,49 +0,0 @@ -package http - -import ( - "context" - "net/url" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2" -) - -// TODO remove this file once bucket and org service are moved to the tenant service - -func decodeIDFromCtx(ctx context.Context, name string) (influxdb.ID, error) { - params := httprouter.ParamsFromContext(ctx) - idStr := params.ByName(name) - - if idStr == "" { - return 0, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "url missing " + name, - } - } - - var i influxdb.ID - if err := i.DecodeFromString(idStr); err != nil { - return 0, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - return i, nil -} - -func decodeIDFromQuery(v url.Values, key string) (influxdb.ID, error) { - idStr := v.Get(key) - if idStr == "" { - return 0, nil - } - - id, err := influxdb.IDFromString(idStr) - if err != nil { - return 0, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - return *id, nil -} diff --git a/http/label_service.go b/http/label_service.go index a4e4f74089..b8f7e4d98e 100644 --- a/http/label_service.go +++ b/http/label_service.go @@ -637,6 +637,10 @@ func (s *LabelService) DeleteLabelMapping(ctx context.Context, m *influxdb.Label } return s.Client. - Delete(resourceIDPath(m.ResourceType, m.ResourceID, "labels")). + Delete(resourceIDMappingPath(m.ResourceType, m.ResourceID, "labels", m.LabelID)). Do(ctx) } + +func resourceIDMappingPath(resourceType influxdb.ResourceType, resourceID influxdb.ID, p string, labelID influxdb.ID) string { + return path.Join("/api/v2/", string(resourceType), resourceID.String(), p, labelID.String()) +} diff --git a/http/label_test.go b/http/label_test.go index e233e36d4b..d3aa1943f8 100644 --- a/http/label_test.go +++ b/http/label_test.go @@ -13,7 +13,7 @@ import ( "github.com/influxdata/httprouter" platform "github.com/influxdata/influxdb/v2" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2/label" "github.com/influxdata/influxdb/v2/mock" platformtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" @@ -595,23 +595,34 @@ func TestService_handlePatchLabel(t *testing.T) { func initLabelService(f platformtesting.LabelFields, t *testing.T) (platform.LabelService, string, func()) { store := NewTestInmemStore(t) - svc := kv.NewService(zaptest.NewLogger(t), store) - svc.IDGenerator = f.IDGenerator + + labelStore, err := label.NewStore(store) + if err != nil { + t.Fatal(err) + } + + if f.IDGenerator != nil { + labelStore.IDGenerator = f.IDGenerator + } + + labelService := label.NewService(labelStore) ctx := context.Background() for _, l := range f.Labels { - if err := svc.PutLabel(ctx, l); err != nil { - t.Fatalf("failed to populate labels: %v", err) - } + mock.SetIDForFunc(&labelStore.IDGenerator, l.ID, func() { + if err := labelService.CreateLabel(ctx, l); err != nil { + t.Fatalf("failed to populate labels: %v", err) + } + }) } for _, m := range f.Mappings { - if err := svc.PutLabelMapping(ctx, m); err != nil { + if err := labelService.CreateLabelMapping(ctx, m); err != nil { t.Fatalf("failed to populate label mappings: %v", err) } } - handler := NewLabelHandler(zaptest.NewLogger(t), svc, kithttp.ErrorHandler(0)) + handler := NewLabelHandler(zaptest.NewLogger(t), labelService, kithttp.ErrorHandler(0)) server := httptest.NewServer(handler) client := LabelService{ Client: mustNewHTTPClient(t, server.URL, ""), diff --git a/http/middleware.go b/http/middleware.go index dc57eaae79..e66b157545 100644 --- a/http/middleware.go +++ b/http/middleware.go @@ -125,6 +125,12 @@ func ignoreMethod(ignoredMethods ...string) isValidMethodFn { } } +const ( + prefixSetup = "/api/v2/setup" + organizationsIDSecretsPath = "/api/v2/orgs/:id/secrets" + organizationsIDSecretsDeletePath = "/api/v2/orgs/:id/secrets/delete" +) + // TODO(@jsteenb2): make this a stronger type that handlers can register routes that should not be logged. var blacklistEndpoints = map[string]isValidMethodFn{ prefixSignIn: ignoreMethod(), @@ -153,15 +159,3 @@ func (b *bodyEchoer) Read(p []byte) (int, error) { func (b *bodyEchoer) Close() error { return b.rc.Close() } - -func applyMW(h http.Handler, m ...kithttp.Middleware) http.Handler { - if len(m) < 1 { - return h - } - wrapped := h - - for i := len(m) - 1; i >= 0; i-- { - wrapped = m[i](wrapped) - } - return wrapped -} diff --git a/http/notification_endpoint_test.go b/http/notification_endpoint_test.go index cb8d38576b..181595f024 100644 --- a/http/notification_endpoint_test.go +++ b/http/notification_endpoint_test.go @@ -21,8 +21,10 @@ import ( "github.com/influxdata/influxdb/v2/notification/endpoint/service" endpointTesting "github.com/influxdata/influxdb/v2/notification/endpoint/service/testing" "github.com/influxdata/influxdb/v2/pkg/testttp" + "github.com/influxdata/influxdb/v2/secret" "github.com/influxdata/influxdb/v2/tenant" influxTesting "github.com/influxdata/influxdb/v2/testing" + "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -1065,17 +1067,22 @@ func initNotificationEndpointService(f endpointTesting.NotificationEndpointField ctx := context.Background() store := NewTestInmemStore(t) logger := zaptest.NewLogger(t) - kvSvc := kv.NewService(logger, store) + + tenantStore := tenant.NewStore(store) + tenantService := tenant.NewService(tenantStore) + + kvSvc := kv.NewService(logger, store, tenantService) kvSvc.IDGenerator = f.IDGenerator kvSvc.TimeGenerator = f.TimeGenerator + secretStore, err := secret.NewStore(store) + require.NoError(t, err) + secretSvc := secret.NewService(secretStore) + endpointStore := service.NewStore(store) endpointStore.IDGenerator = f.IDGenerator endpointStore.TimeGenerator = f.TimeGenerator - endpointService := service.New(endpointStore, kvSvc) - - tenantStore := tenant.NewStore(store) - tenantService := tenant.NewService(tenantStore) + endpointService := service.New(endpointStore, secretSvc) for _, o := range f.Orgs { withOrgID(tenantStore, o.ID, func() { @@ -1107,7 +1114,8 @@ func initNotificationEndpointService(f endpointTesting.NotificationEndpointField done := server.Close client := mustNewHTTPClient(t, server.URL, "") - return NewNotificationEndpointService(client), kvSvc, done + + return NewNotificationEndpointService(client), secretSvc, done } func TestNotificationEndpointService(t *testing.T) { diff --git a/http/notification_rule.go b/http/notification_rule.go index 44afb2d655..fd7836059c 100644 --- a/http/notification_rule.go +++ b/http/notification_rule.go @@ -731,20 +731,12 @@ func (h *NotificationRuleHandler) handleDeleteNotificationRule(w http.ResponseWr // NotificationRuleService is an http client that implements the NotificationRuleStore interface type NotificationRuleService struct { Client *httpc.Client - *UserResourceMappingService - *OrganizationService } // NewNotificationRuleService wraps an httpc.Client in a NotificationRuleService func NewNotificationRuleService(client *httpc.Client) *NotificationRuleService { return &NotificationRuleService{ Client: client, - UserResourceMappingService: &UserResourceMappingService{ - Client: client, - }, - OrganizationService: &OrganizationService{ - Client: client, - }, } } diff --git a/http/onboarding.go b/http/onboarding.go deleted file mode 100644 index 2dc7deec47..0000000000 --- a/http/onboarding.go +++ /dev/null @@ -1,217 +0,0 @@ -package http - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - - "github.com/influxdata/httprouter" - platform "github.com/influxdata/influxdb/v2" - "go.uber.org/zap" -) - -// SetupBackend is all services and associated parameters required to construct -// the SetupHandler. -type SetupBackend struct { - platform.HTTPErrorHandler - log *zap.Logger - OnboardingService platform.OnboardingService -} - -// NewSetupBackend returns a new instance of SetupBackend. -func NewSetupBackend(log *zap.Logger, b *APIBackend) *SetupBackend { - return &SetupBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: log, - // OnboardingService: b.OnboardingService, - } -} - -// SetupHandler represents an HTTP API handler for onboarding setup. -type SetupHandler struct { - *httprouter.Router - platform.HTTPErrorHandler - log *zap.Logger - - OnboardingService platform.OnboardingService -} - -const ( - prefixSetup = "/api/v2/setup" -) - -// NewSetupHandler returns a new instance of SetupHandler. -func NewSetupHandler(log *zap.Logger, b *SetupBackend) *SetupHandler { - h := &SetupHandler{ - Router: NewRouter(b.HTTPErrorHandler), - HTTPErrorHandler: b.HTTPErrorHandler, - log: log, - OnboardingService: b.OnboardingService, - } - h.HandlerFunc("POST", prefixSetup, h.handlePostSetup) - h.HandlerFunc("GET", prefixSetup, h.isOnboarding) - return h -} - -type isOnboardingResponse struct { - Allowed bool `json:"allowed"` -} - -// isOnboarding is the HTTP handler for the GET /api/v2/setup route. -func (h *SetupHandler) isOnboarding(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - result, err := h.OnboardingService.IsOnboarding(ctx) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - h.log.Debug("Onboarding eligibility check finished", zap.String("result", fmt.Sprint(result))) - - if err := encodeResponse(ctx, w, http.StatusOK, isOnboardingResponse{result}); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -// isOnboarding is the HTTP handler for the POST /api/v2/setup route. -func (h *SetupHandler) handlePostSetup(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - req, err := decodePostSetupRequest(ctx, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - results, err := h.OnboardingService.OnboardInitialUser(ctx, req) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - h.log.Debug("Onboarding setup completed", zap.String("results", fmt.Sprint(results))) - - if err := encodeResponse(ctx, w, http.StatusCreated, newOnboardingResponse(results)); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -type onboardingResponse struct { - User *UserResponse `json:"user"` - Bucket *bucketResponse `json:"bucket"` - Organization orgResponse `json:"org"` - Auth *authResponse `json:"auth"` -} - -func newOnboardingResponse(results *platform.OnboardingResults) *onboardingResponse { - // when onboarding the permissions are for all resources and no - // specifically named resources. Therefore, there is no need to - // lookup the name. - ps := make([]permissionResponse, len(results.Auth.Permissions)) - for i, p := range results.Auth.Permissions { - ps[i] = permissionResponse{ - Action: p.Action, - Resource: resourceResponse{ - Resource: p.Resource, - }, - } - } - return &onboardingResponse{ - User: newUserResponse(results.User), - Bucket: NewBucketResponse(results.Bucket, []*platform.Label{}), - Organization: newOrgResponse(*results.Org), - Auth: newAuthResponse(results.Auth, results.Org, results.User, ps), - } -} - -func decodePostSetupRequest(ctx context.Context, r *http.Request) (*platform.OnboardingRequest, error) { - req := &platform.OnboardingRequest{} - if err := json.NewDecoder(r.Body).Decode(req); err != nil { - return nil, err - } - - return req, nil -} - -// SetupService connects to Influx via HTTP to perform onboarding operations -type SetupService struct { - Addr string - InsecureSkipVerify bool -} - -// IsOnboarding determine if onboarding request is allowed. -func (s *SetupService) IsOnboarding(ctx context.Context) (bool, error) { - u, err := NewURL(s.Addr, prefixSetup) - if err != nil { - return false, err - } - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return false, err - } - hc := NewClient(u.Scheme, s.InsecureSkipVerify) - resp, err := hc.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - - if err := CheckError(resp); err != nil { - return false, err - } - var ir isOnboardingResponse - if err := json.NewDecoder(resp.Body).Decode(&ir); err != nil { - return false, err - } - return ir.Allowed, nil -} - -// OnboardInitialUser OnboardingResults. -func (s *SetupService) OnboardInitialUser(ctx context.Context, or *platform.OnboardingRequest) (*platform.OnboardingResults, error) { - u, err := NewURL(s.Addr, prefixSetup) - if err != nil { - return nil, err - } - octets, err := json.Marshal(or) - if err != nil { - return nil, err - } - req, err := http.NewRequest("POST", u.String(), bytes.NewReader(octets)) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - hc := NewClient(u.Scheme, s.InsecureSkipVerify) - - resp, err := hc.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - // TODO(jsternberg): Should this check for a 201 explicitly? - if err := CheckError(resp); err != nil { - return nil, err - } - - var oResp onboardingResponse - if err := json.NewDecoder(resp.Body).Decode(&oResp); err != nil { - return nil, err - } - - bkt, err := oResp.Bucket.toInfluxDB() - if err != nil { - return nil, err - } - return &platform.OnboardingResults{ - Org: &oResp.Organization.Organization, - User: &oResp.User.User, - Auth: oResp.Auth.toPlatform(), - Bucket: bkt, - }, nil -} - -func (s *SetupService) OnboardUser(ctx context.Context, or *platform.OnboardingRequest) (*platform.OnboardingResults, error) { - return nil, errors.New("not yet implemented") -} diff --git a/http/onboarding_test.go b/http/onboarding_test.go deleted file mode 100644 index 9814b20edb..0000000000 --- a/http/onboarding_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package http - -import ( - "context" - "net/http/httptest" - "testing" - - platform "github.com/influxdata/influxdb/v2" - kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/mock" - platformtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -// NewMockSetupBackend returns a SetupBackend with mock services. -func NewMockSetupBackend(t *testing.T) *SetupBackend { - return &SetupBackend{ - log: zaptest.NewLogger(t), - OnboardingService: mock.NewOnboardingService(), - } -} - -func initOnboardingService(f platformtesting.OnboardingFields, t *testing.T) (platform.OnboardingService, func()) { - t.Helper() - store := NewTestInmemStore(t) - svc := kv.NewService(zaptest.NewLogger(t), store) - svc.IDGenerator = f.IDGenerator - svc.OrgIDs = f.IDGenerator - svc.BucketIDs = f.IDGenerator - svc.TokenGenerator = f.TokenGenerator - if f.TimeGenerator == nil { - f.TimeGenerator = platform.RealTimeGenerator{} - } - svc.TimeGenerator = f.TimeGenerator - - ctx := context.Background() - if err := svc.PutOnboardingStatus(ctx, !f.IsOnboarding); err != nil { - t.Fatalf("failed to set new onboarding finished: %v", err) - } - - setupBackend := NewMockSetupBackend(t) - setupBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - setupBackend.OnboardingService = svc - handler := NewSetupHandler(zaptest.NewLogger(t), setupBackend) - server := httptest.NewServer(handler) - client := struct { - *SetupService - *Service - platform.PasswordsService - }{ - SetupService: &SetupService{ - Addr: server.URL, - }, - Service: &Service{ - Addr: server.URL, - }, - PasswordsService: svc, - } - - done := server.Close - - return client, done -} -func TestOnboardingService(t *testing.T) { - platformtesting.OnboardInitialUser(initOnboardingService, t) -} diff --git a/http/org_service.go b/http/org_service.go deleted file mode 100644 index 958ef1acd2..0000000000 --- a/http/org_service.go +++ /dev/null @@ -1,624 +0,0 @@ -package http - -import ( - "context" - "fmt" - "net/http" - "path" - "strings" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kit/tracing" - kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/pkg/httpc" - "go.uber.org/zap" -) - -// OrgBackend is all services and associated parameters required to construct -// the OrgHandler. -type OrgBackend struct { - influxdb.HTTPErrorHandler - log *zap.Logger - - OrganizationService influxdb.OrganizationService - OrganizationOperationLogService influxdb.OrganizationOperationLogService - UserResourceMappingService influxdb.UserResourceMappingService - SecretService influxdb.SecretService - LabelService influxdb.LabelService - UserService influxdb.UserService -} - -// NewOrgBackend is a datasource used by the org handler. -func NewOrgBackend(log *zap.Logger, b *APIBackend) *OrgBackend { - return &OrgBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: log, - - OrganizationService: b.OrganizationService, - OrganizationOperationLogService: b.OrganizationOperationLogService, - UserResourceMappingService: b.UserResourceMappingService, - SecretService: b.SecretService, - LabelService: b.LabelService, - UserService: b.UserService, - } -} - -// OrgHandler represents an HTTP API handler for orgs. -type OrgHandler struct { - *httprouter.Router - *kithttp.API - log *zap.Logger - - OrgSVC influxdb.OrganizationService - OrganizationOperationLogService influxdb.OrganizationOperationLogService - UserResourceMappingService influxdb.UserResourceMappingService - SecretService influxdb.SecretService - LabelService influxdb.LabelService - UserService influxdb.UserService -} - -const ( - prefixOrganizations = "/api/v2/orgs" - organizationsIDPath = "/api/v2/orgs/:id" - organizationsIDMembersPath = "/api/v2/orgs/:id/members" - organizationsIDMembersIDPath = "/api/v2/orgs/:id/members/:userID" - organizationsIDOwnersPath = "/api/v2/orgs/:id/owners" - organizationsIDOwnersIDPath = "/api/v2/orgs/:id/owners/:userID" - organizationsIDSecretsPath = "/api/v2/orgs/:id/secrets" - // TODO(desa): need a way to specify which secrets to delete. this should work for now - organizationsIDSecretsDeletePath = "/api/v2/orgs/:id/secrets/delete" - organizationsIDLabelsPath = "/api/v2/orgs/:id/labels" - organizationsIDLabelsIDPath = "/api/v2/orgs/:id/labels/:lid" -) - -func checkOrganizationExists(orgHandler *OrgHandler) kithttp.Middleware { - fn := func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - orgID, err := decodeIDFromCtx(ctx, "id") - if err != nil { - orgHandler.API.Err(w, r, err) - return - } - - if _, err := orgHandler.OrgSVC.FindOrganizationByID(ctx, orgID); err != nil { - orgHandler.API.Err(w, r, err) - return - } - next.ServeHTTP(w, r) - }) - } - - return fn -} - -// NewOrgHandler returns a new instance of OrgHandler. -func NewOrgHandler(log *zap.Logger, b *OrgBackend) *OrgHandler { - h := &OrgHandler{ - Router: NewRouter(b.HTTPErrorHandler), - API: kithttp.NewAPI(kithttp.WithLog(log)), - log: log, - - OrgSVC: b.OrganizationService, - OrganizationOperationLogService: b.OrganizationOperationLogService, - UserResourceMappingService: b.UserResourceMappingService, - SecretService: b.SecretService, - LabelService: b.LabelService, - UserService: b.UserService, - } - - h.HandlerFunc("POST", prefixOrganizations, h.handlePostOrg) - h.HandlerFunc("GET", prefixOrganizations, h.handleGetOrgs) - h.HandlerFunc("GET", organizationsIDPath, h.handleGetOrg) - h.HandlerFunc("PATCH", organizationsIDPath, h.handlePatchOrg) - h.HandlerFunc("DELETE", organizationsIDPath, h.handleDeleteOrg) - - memberBackend := MemberBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: b.log.With(zap.String("handler", "member")), - ResourceType: influxdb.OrgsResourceType, - UserType: influxdb.Member, - UserResourceMappingService: b.UserResourceMappingService, - UserService: b.UserService, - } - h.HandlerFunc("POST", organizationsIDMembersPath, newPostMemberHandler(memberBackend)) - h.Handler("GET", organizationsIDMembersPath, applyMW(newGetMembersHandler(memberBackend), checkOrganizationExists(h))) - h.HandlerFunc("DELETE", organizationsIDMembersIDPath, newDeleteMemberHandler(memberBackend)) - - ownerBackend := MemberBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: b.log.With(zap.String("handler", "member")), - ResourceType: influxdb.OrgsResourceType, - UserType: influxdb.Owner, - UserResourceMappingService: b.UserResourceMappingService, - UserService: b.UserService, - } - h.HandlerFunc("POST", organizationsIDOwnersPath, newPostMemberHandler(ownerBackend)) - h.Handler("GET", organizationsIDOwnersPath, applyMW(newGetMembersHandler(ownerBackend), checkOrganizationExists(h))) - h.HandlerFunc("DELETE", organizationsIDOwnersIDPath, newDeleteMemberHandler(ownerBackend)) - - h.HandlerFunc("GET", organizationsIDSecretsPath, h.handleGetSecrets) - h.HandlerFunc("PATCH", organizationsIDSecretsPath, h.handlePatchSecrets) - // TODO(desa): need a way to specify which secrets to delete. this should work for now - h.HandlerFunc("POST", organizationsIDSecretsDeletePath, h.handleDeleteSecrets) - - labelBackend := &LabelBackend{ - HTTPErrorHandler: b.HTTPErrorHandler, - log: b.log.With(zap.String("handler", "label")), - LabelService: b.LabelService, - ResourceType: influxdb.OrgsResourceType, - } - h.HandlerFunc("GET", organizationsIDLabelsPath, newGetLabelsHandler(labelBackend)) - h.HandlerFunc("POST", organizationsIDLabelsPath, newPostLabelHandler(labelBackend)) - h.HandlerFunc("DELETE", organizationsIDLabelsIDPath, newDeleteLabelHandler(labelBackend)) - - return h -} - -type orgsResponse struct { - Links map[string]string `json:"links"` - Organizations []orgResponse `json:"orgs"` -} - -func (o orgsResponse) toInfluxdb() []*influxdb.Organization { - orgs := make([]*influxdb.Organization, len(o.Organizations)) - for i := range o.Organizations { - orgs[i] = &o.Organizations[i].Organization - } - return orgs -} - -func newOrgsResponse(orgs []*influxdb.Organization) *orgsResponse { - res := orgsResponse{ - Links: map[string]string{ - "self": "/api/v2/orgs", - }, - Organizations: []orgResponse{}, - } - for _, org := range orgs { - res.Organizations = append(res.Organizations, newOrgResponse(*org)) - } - return &res -} - -type orgResponse struct { - Links map[string]string `json:"links"` - influxdb.Organization -} - -func newOrgResponse(o influxdb.Organization) orgResponse { - return orgResponse{ - Links: map[string]string{ - "self": fmt.Sprintf("/api/v2/orgs/%s", o.ID), - "logs": fmt.Sprintf("/api/v2/orgs/%s/logs", o.ID), - "members": fmt.Sprintf("/api/v2/orgs/%s/members", o.ID), - "owners": fmt.Sprintf("/api/v2/orgs/%s/owners", o.ID), - "secrets": fmt.Sprintf("/api/v2/orgs/%s/secrets", o.ID), - "labels": fmt.Sprintf("/api/v2/orgs/%s/labels", o.ID), - "buckets": fmt.Sprintf("/api/v2/buckets?org=%s", o.Name), - "tasks": fmt.Sprintf("/api/v2/tasks?org=%s", o.Name), - "dashboards": fmt.Sprintf("/api/v2/dashboards?org=%s", o.Name), - }, - Organization: o, - } -} - -// handlePostOrg is the HTTP handler for the POST /api/v2/orgs route. -func (h *OrgHandler) handlePostOrg(w http.ResponseWriter, r *http.Request) { - var org influxdb.Organization - if err := h.API.DecodeJSON(r.Body, &org); err != nil { - h.API.Err(w, r, err) - return - } - - if err := h.OrgSVC.CreateOrganization(r.Context(), &org); err != nil { - h.API.Err(w, r, err) - return - } - h.log.Debug("Org created", zap.String("org", fmt.Sprint(org))) - - h.API.Respond(w, r, http.StatusCreated, newOrgResponse(org)) -} - -// handleGetOrg is the HTTP handler for the GET /api/v2/orgs/:id route. -func (h *OrgHandler) handleGetOrg(w http.ResponseWriter, r *http.Request) { - id, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.API.Err(w, r, err) - return - } - - org, err := h.OrgSVC.FindOrganizationByID(r.Context(), id) - if err != nil { - h.API.Err(w, r, err) - return - } - h.log.Debug("Org retrieved", zap.String("org", fmt.Sprint(org))) - - h.API.Respond(w, r, http.StatusOK, newOrgResponse(*org)) -} - -// handleGetOrgs is the HTTP handler for the GET /api/v2/orgs route. -func (h *OrgHandler) handleGetOrgs(w http.ResponseWriter, r *http.Request) { - qp := r.URL.Query() - - opts, err := influxdb.DecodeFindOptions(r) - if err != nil { - h.API.Err(w, r, err) - return - } - - var filter influxdb.OrganizationFilter - if name := qp.Get(Org); name != "" { - filter.Name = &name - } - if orgID := qp.Get("orgID"); orgID != "" { - id, err := influxdb.IDFromString(orgID) - if err != nil { - h.API.Err(w, r, err) - return - } - filter.ID = id - } - - if userID := qp.Get("userID"); userID != "" { - id, err := influxdb.IDFromString(userID) - if err != nil { - h.API.Err(w, r, err) - return - } - filter.UserID = id - } - - orgs, _, err := h.OrgSVC.FindOrganizations(r.Context(), filter, *opts) - if err != nil { - h.API.Err(w, r, err) - return - } - h.log.Debug("Orgs retrieved", zap.String("org", fmt.Sprint(orgs))) - - h.API.Respond(w, r, http.StatusOK, newOrgsResponse(orgs)) -} - -// handleDeleteOrganization is the HTTP handler for the DELETE /api/v2/orgs/:id route. -func (h *OrgHandler) handleDeleteOrg(w http.ResponseWriter, r *http.Request) { - id, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.API.Err(w, r, err) - return - } - - ctx := r.Context() - if err := h.OrgSVC.DeleteOrganization(ctx, id); err != nil { - h.API.Err(w, r, err) - return - } - h.log.Debug("Org deleted", zap.String("orgID", fmt.Sprint(id))) - - h.API.Respond(w, r, http.StatusNoContent, nil) -} - -// handlePatchOrg is the HTTP handler for the PATH /api/v2/orgs route. -func (h *OrgHandler) handlePatchOrg(w http.ResponseWriter, r *http.Request) { - id, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.API.Err(w, r, err) - return - } - - var upd influxdb.OrganizationUpdate - if err := h.API.DecodeJSON(r.Body, &upd); err != nil { - h.API.Err(w, r, err) - return - } - - org, err := h.OrgSVC.UpdateOrganization(r.Context(), id, upd) - if err != nil { - h.API.Err(w, r, err) - return - } - h.log.Debug("Org updated", zap.String("org", fmt.Sprint(org))) - - h.API.Respond(w, r, http.StatusOK, newOrgResponse(*org)) -} - -type secretsResponse struct { - Links map[string]string `json:"links"` - Secrets []string `json:"secrets"` -} - -func newSecretsResponse(orgID influxdb.ID, ks []string) *secretsResponse { - if ks == nil { - ks = []string{} - } - return &secretsResponse{ - Links: map[string]string{ - "org": fmt.Sprintf("/api/v2/orgs/%s", orgID), - "self": fmt.Sprintf("/api/v2/orgs/%s/secrets", orgID), - }, - Secrets: ks, - } -} - -// handleGetSecrets is the HTTP handler for the GET /api/v2/orgs/:id/secrets route. -func (h *OrgHandler) handleGetSecrets(w http.ResponseWriter, r *http.Request) { - orgID, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.API.Err(w, r, err) - return - } - - ks, err := h.SecretService.GetSecretKeys(r.Context(), orgID) - if err != nil && influxdb.ErrorCode(err) != influxdb.ENotFound { - h.API.Err(w, r, err) - return - } - - h.API.Respond(w, r, http.StatusOK, newSecretsResponse(orgID, ks)) -} - -// handleGetPatchSecrets is the HTTP handler for the PATCH /api/v2/orgs/:id/secrets route. -func (h *OrgHandler) handlePatchSecrets(w http.ResponseWriter, r *http.Request) { - orgID, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.API.Err(w, r, err) - return - } - - var secrets map[string]string - if err := h.API.DecodeJSON(r.Body, &secrets); err != nil { - h.API.Err(w, r, err) - return - } - - if err := h.SecretService.PatchSecrets(r.Context(), orgID, secrets); err != nil { - h.API.Err(w, r, err) - return - } - - h.API.Respond(w, r, http.StatusNoContent, nil) -} - -type secretsDeleteBody struct { - Secrets []string `json:"secrets"` -} - -// handleDeleteSecrets is the HTTP handler for the DELETE /api/v2/orgs/:id/secrets route. -func (h *OrgHandler) handleDeleteSecrets(w http.ResponseWriter, r *http.Request) { - orgID, err := decodeIDFromCtx(r.Context(), "id") - if err != nil { - h.API.Err(w, r, err) - return - } - - var reqBody secretsDeleteBody - - if err := h.API.DecodeJSON(r.Body, &reqBody); err != nil { - h.API.Err(w, r, err) - return - } - - if err := h.SecretService.DeleteSecret(r.Context(), orgID, reqBody.Secrets...); err != nil { - h.API.Err(w, r, err) - return - } - - h.API.Respond(w, r, http.StatusNoContent, nil) -} - -// SecretService connects to Influx via HTTP using tokens to manage secrets. -type SecretService struct { - Client *httpc.Client -} - -// LoadSecret is not implemented for http -func (s *SecretService) LoadSecret(ctx context.Context, orgID influxdb.ID, k string) (string, error) { - return "", &influxdb.Error{ - Code: influxdb.EMethodNotAllowed, - Msg: "load secret is not implemented for http", - } -} - -// PutSecret is not implemented for http. -func (s *SecretService) PutSecret(ctx context.Context, orgID influxdb.ID, k string, v string) error { - return &influxdb.Error{ - Code: influxdb.EMethodNotAllowed, - Msg: "put secret is not implemented for http", - } -} - -// GetSecretKeys get all secret keys mathing an org ID via HTTP. -func (s *SecretService) GetSecretKeys(ctx context.Context, orgID influxdb.ID) ([]string, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - span.LogKV("org-id", orgID) - - path := strings.Replace(organizationsIDSecretsPath, ":id", orgID.String(), 1) - - var ss secretsResponse - err := s.Client. - Get(path). - DecodeJSON(&ss). - Do(ctx) - if err != nil { - return nil, err - } - - return ss.Secrets, nil -} - -// PutSecrets is not implemented for http. -func (s *SecretService) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error { - return &influxdb.Error{ - Code: influxdb.EMethodNotAllowed, - Msg: "put secrets is not implemented for http", - } -} - -// PatchSecrets will update the existing secret with new via http. -func (s *SecretService) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - if orgID != 0 { - span.LogKV("org-id", orgID) - } - - path := strings.Replace(organizationsIDSecretsPath, ":id", orgID.String(), 1) - - return s.Client. - PatchJSON(m, path). - Do(ctx) -} - -// DeleteSecret removes a single secret via HTTP. -func (s *SecretService) DeleteSecret(ctx context.Context, orgID influxdb.ID, ks ...string) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - path := strings.Replace(organizationsIDSecretsDeletePath, ":id", orgID.String(), 1) - return s.Client. - PostJSON(secretsDeleteBody{ - Secrets: ks, - }, path). - Do(ctx) -} - -// OrganizationService connects to Influx via HTTP using tokens to manage organizations. -type OrganizationService struct { - Client *httpc.Client - // OpPrefix is for not found errors. - OpPrefix string -} - -// FindOrganizationByID gets a single organization with a given id using HTTP. -func (s *OrganizationService) FindOrganizationByID(ctx context.Context, id influxdb.ID) (*influxdb.Organization, error) { - filter := influxdb.OrganizationFilter{ID: &id} - o, err := s.FindOrganization(ctx, filter) - if err != nil { - return nil, &influxdb.Error{ - Err: err, - Op: s.OpPrefix + influxdb.OpFindOrganizationByID, - } - } - return o, nil -} - -// FindOrganization gets a single organization matching the filter using HTTP. -func (s *OrganizationService) FindOrganization(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) { - if filter.ID == nil && filter.Name == nil { - return nil, influxdb.ErrInvalidOrgFilter - } - os, n, err := s.FindOrganizations(ctx, filter) - if err != nil { - return nil, &influxdb.Error{ - Err: err, - Op: s.OpPrefix + influxdb.OpFindOrganization, - } - } - - if n == 0 { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Op: s.OpPrefix + influxdb.OpFindOrganization, - Msg: "organization not found", - } - } - - return os[0], nil -} - -// FindOrganizations returns all organizations that match the filter via HTTP. -func (s *OrganizationService) FindOrganizations(ctx context.Context, filter influxdb.OrganizationFilter, opt ...influxdb.FindOptions) ([]*influxdb.Organization, int, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - params := influxdb.FindOptionParams(opt...) - if filter.Name != nil { - span.LogKV("org", *filter.Name) - params = append(params, [2]string{"org", *filter.Name}) - } - if filter.ID != nil { - span.LogKV("org-id", *filter.ID) - params = append(params, [2]string{"orgID", filter.ID.String()}) - } - for _, o := range opt { - if o.Offset != 0 { - span.LogKV("offset", o.Offset) - } - span.LogKV("descending", o.Descending) - if o.Limit > 0 { - span.LogKV("limit", o.Limit) - } - if o.SortBy != "" { - span.LogKV("sortBy", o.SortBy) - } - } - - var os orgsResponse - err := s.Client. - Get(prefixOrganizations). - QueryParams(params...). - DecodeJSON(&os). - Do(ctx) - if err != nil { - return nil, 0, err - } - - orgs := os.toInfluxdb() - return orgs, len(orgs), nil -} - -// CreateOrganization creates an organization. -func (s *OrganizationService) CreateOrganization(ctx context.Context, o *influxdb.Organization) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - if o.Name != "" { - span.LogKV("org", o.Name) - } - if o.ID != 0 { - span.LogKV("org-id", o.ID) - } - - return s.Client. - PostJSON(o, prefixOrganizations). - DecodeJSON(o). - Do(ctx) -} - -// UpdateOrganization updates the organization over HTTP. -func (s *OrganizationService) UpdateOrganization(ctx context.Context, id influxdb.ID, upd influxdb.OrganizationUpdate) (*influxdb.Organization, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - span.LogKV("org-id", id) - span.LogKV("name", upd.Name) - - var o influxdb.Organization - err := s.Client. - PatchJSON(upd, prefixOrganizations, id.String()). - DecodeJSON(&o). - Do(ctx) - if err != nil { - return nil, tracing.LogError(span, err) - } - - return &o, nil -} - -// DeleteOrganization removes organization id over HTTP. -func (s *OrganizationService) DeleteOrganization(ctx context.Context, id influxdb.ID) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - return s.Client. - Delete(prefixOrganizations, id.String()). - Do(ctx) -} - -func organizationIDPath(id influxdb.ID) string { - return path.Join(prefixOrganizations, id.String()) -} diff --git a/http/org_service_test.go b/http/org_service_test.go deleted file mode 100644 index ce06faa140..0000000000 --- a/http/org_service_test.go +++ /dev/null @@ -1,426 +0,0 @@ -package http - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/inmem" - kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/kv/migration/all" - "github.com/influxdata/influxdb/v2/mock" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -// NewMockOrgBackend returns a OrgBackend with mock services. -func NewMockOrgBackend(t *testing.T) *OrgBackend { - return &OrgBackend{ - log: zaptest.NewLogger(t), - - OrganizationService: mock.NewOrganizationService(), - OrganizationOperationLogService: mock.NewOrganizationOperationLogService(), - UserResourceMappingService: mock.NewUserResourceMappingService(), - SecretService: mock.NewSecretService(), - LabelService: mock.NewLabelService(), - UserService: mock.NewUserService(), - } -} - -func initOrganizationService(f influxdbtesting.OrganizationFields, t *testing.T) (influxdb.OrganizationService, string, func()) { - t.Helper() - - ctx := context.Background() - logger := zaptest.NewLogger(t) - store := NewTestInmemStore(t) - svc := kv.NewService(logger, store) - svc.IDGenerator = f.IDGenerator - svc.OrgIDs = f.OrgBucketIDs - svc.BucketIDs = f.OrgBucketIDs - svc.TimeGenerator = f.TimeGenerator - if f.TimeGenerator == nil { - svc.TimeGenerator = influxdb.RealTimeGenerator{} - } - - for _, o := range f.Organizations { - // PutOrgs no longer creates an ID - // that is what CreateOrganization does - // so we have to generate one - o.ID = svc.OrgIDs.ID() - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate organizations") - } - } - - orgBackend := NewMockOrgBackend(t) - orgBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - orgBackend.OrganizationService = svc - orgBackend.SecretService = svc - handler := NewOrgHandler(zaptest.NewLogger(t), orgBackend) - server := httptest.NewServer(handler) - client := OrganizationService{ - Client: mustNewHTTPClient(t, server.URL, ""), - } - done := server.Close - - return &client, "", done -} - -func initSecretService(f influxdbtesting.SecretServiceFields, t *testing.T) (influxdb.SecretService, func()) { - t.Helper() - - ctx := context.Background() - store := inmem.NewKVStore() - logger := zaptest.NewLogger(t) - if err := all.Up(ctx, logger, store); err != nil { - t.Fatal(err) - } - - svc := kv.NewService(logger, store) - - for _, ss := range f.Secrets { - if err := svc.PutSecrets(ctx, ss.OrganizationID, ss.Env); err != nil { - t.Fatalf("failed to populate secrets") - } - } - - scrBackend := NewMockOrgBackend(t) - scrBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - scrBackend.SecretService = svc - handler := NewOrgHandler(zaptest.NewLogger(t), scrBackend) - server := httptest.NewServer(handler) - client := SecretService{ - Client: mustNewHTTPClient(t, server.URL, ""), - } - done := server.Close - - return &client, done -} - -func TestOrganizationService(t *testing.T) { - t.Parallel() - influxdbtesting.OrganizationService(initOrganizationService, t) -} - -func TestSecretService(t *testing.T) { - t.Parallel() - influxdbtesting.DeleteSecrets(initSecretService, t) - influxdbtesting.GetSecretKeys(initSecretService, t) - influxdbtesting.PatchSecrets(initSecretService, t) -} - -func TestSecretService_handleGetSecrets(t *testing.T) { - type fields struct { - SecretService influxdb.SecretService - } - type args struct { - orgID influxdb.ID - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "get basic secrets", - fields: fields{ - &mock.SecretService{ - GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) { - return []string{"hello", "world"}, nil - }, - }, - }, - args: args{ - orgID: 1, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "org": "/api/v2/orgs/0000000000000001", - "self": "/api/v2/orgs/0000000000000001/secrets" - }, - "secrets": [ - "hello", - "world" - ] -} -`, - }, - }, - { - name: "get secrets when there are none", - fields: fields{ - &mock.SecretService{ - GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) { - return []string{}, nil - }, - }, - }, - args: args{ - orgID: 1, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "org": "/api/v2/orgs/0000000000000001", - "self": "/api/v2/orgs/0000000000000001/secrets" - }, - "secrets": [] -} -`, - }, - }, - { - name: "get secrets when organization has no secret keys", - fields: fields{ - &mock.SecretService{ - GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) { - return []string{}, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "organization has no secret keys", - } - - }, - }, - }, - args: args{ - orgID: 1, - }, - wants: wants{ - statusCode: http.StatusOK, - contentType: "application/json; charset=utf-8", - body: ` -{ - "links": { - "org": "/api/v2/orgs/0000000000000001", - "self": "/api/v2/orgs/0000000000000001/secrets" - }, - "secrets": [] -} -`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - orgBackend := NewMockOrgBackend(t) - orgBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - orgBackend.SecretService = tt.fields.SecretService - h := NewOrgHandler(zaptest.NewLogger(t), orgBackend) - - u := fmt.Sprintf("http://any.url/api/v2/orgs/%s/secrets", tt.args.orgID) - r := httptest.NewRequest("GET", u, nil) - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("handleGetSecrets() = %v, want %v", res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("handleGetSecrets() = %v, want %v", content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handleGetSecrets(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handleGetSecrets() = ***%s***", tt.name, diff) - } - } - - }) - } -} - -func TestSecretService_handlePatchSecrets(t *testing.T) { - type fields struct { - SecretService influxdb.SecretService - } - type args struct { - orgID influxdb.ID - secrets map[string]string - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "get basic secrets", - fields: fields{ - &mock.SecretService{ - PatchSecretsFn: func(ctx context.Context, orgID influxdb.ID, s map[string]string) error { - return nil - }, - }, - }, - args: args{ - orgID: 1, - secrets: map[string]string{ - "abc": "123", - }, - }, - wants: wants{ - statusCode: http.StatusNoContent, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - orgBackend := NewMockOrgBackend(t) - orgBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - orgBackend.SecretService = tt.fields.SecretService - h := NewOrgHandler(zaptest.NewLogger(t), orgBackend) - - b, err := json.Marshal(tt.args.secrets) - if err != nil { - t.Fatalf("failed to marshal secrets: %v", err) - } - - buf := bytes.NewReader(b) - u := fmt.Sprintf("http://any.url/api/v2/orgs/%s/secrets", tt.args.orgID) - r := httptest.NewRequest("PATCH", u, buf) - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("handlePatchSecrets() = %v, want %v", res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("handlePatchSecrets() = %v, want %v", content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handlePatchSecrets(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handlePatchSecrets() = ***%s***", tt.name, diff) - } - } - - }) - } -} - -func TestSecretService_handleDeleteSecrets(t *testing.T) { - type fields struct { - SecretService influxdb.SecretService - } - type args struct { - orgID influxdb.ID - secrets []string - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "get basic secrets", - fields: fields{ - &mock.SecretService{ - DeleteSecretFn: func(ctx context.Context, orgID influxdb.ID, s ...string) error { - return nil - }, - }, - }, - args: args{ - orgID: 1, - secrets: []string{ - "abc", - }, - }, - wants: wants{ - statusCode: http.StatusNoContent, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - orgBackend := NewMockOrgBackend(t) - orgBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - orgBackend.SecretService = tt.fields.SecretService - h := NewOrgHandler(zaptest.NewLogger(t), orgBackend) - - b, err := json.Marshal(struct { - Secrets []string `json:"secrets"` - }{ - Secrets: tt.args.secrets, - }) - if err != nil { - t.Fatalf("failed to marshal secrets: %v", err) - } - - buf := bytes.NewReader(b) - u := fmt.Sprintf("http://any.url/api/v2/orgs/%s/secrets/delete", tt.args.orgID) - r := httptest.NewRequest("POST", u, buf) - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - res := w.Result() - content := res.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(res.Body) - - if res.StatusCode != tt.wants.statusCode { - t.Errorf("handleDeleteSecrets() = %v, want %v", res.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("handleDeleteSecrets() = %v, want %v", content, tt.wants.contentType) - } - if tt.wants.body != "" { - if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { - t.Errorf("%q, handleDeleteSecrets(). error unmarshalling json %v", tt.name, err) - } else if !eq { - t.Errorf("%q. handleDeleteSecrets() = ***%s***", tt.name, diff) - } - } - - }) - } -} diff --git a/http/query_handler_test.go b/http/query_handler_test.go index 9060d05e71..d42d255887 100644 --- a/http/query_handler_test.go +++ b/http/query_handler_test.go @@ -30,6 +30,7 @@ import ( "github.com/influxdata/influxdb/v2/query" "github.com/influxdata/influxdb/v2/query/fluxlang" "github.com/influxdata/influxdb/v2/query/mock" + "github.com/influxdata/influxdb/v2/tenant" "go.uber.org/zap/zaptest" ) @@ -326,7 +327,8 @@ var _ metric.EventRecorder = noopEventRecorder{} func TestFluxHandler_PostQuery_Errors(t *testing.T) { defer tracetesting.SetupInMemoryTracing(t.Name())() - orgSVC := newInMemKVSVC(t) + store := NewTestInmemStore(t) + orgSVC := tenant.NewService(tenant.NewStore(store)) b := &FluxBackend{ HTTPErrorHandler: kithttp.ErrorHandler(0), log: zaptest.NewLogger(t), diff --git a/http/scraper_service.go b/http/scraper_service.go index 403a31f192..31f5b33a87 100644 --- a/http/scraper_service.go +++ b/http/scraper_service.go @@ -14,6 +14,11 @@ import ( "go.uber.org/zap" ) +const ( + prefixOrganizations = "/api/v2/orgs" + prefixBuckets = "/api/v2/buckets" +) + // ScraperBackend is all services and associated parameters required to construct // the ScraperHandler. type ScraperBackend struct { @@ -628,3 +633,11 @@ func (h *ScraperHandler) newTargetResponse(ctx context.Context, target influxdb. return res, nil } + +func organizationIDPath(id influxdb.ID) string { + return path.Join(prefixOrganizations, id.String()) +} + +func bucketIDPath(id influxdb.ID) string { + return path.Join(prefixBuckets, id.String()) +} diff --git a/http/scraper_service_test.go b/http/scraper_service_test.go index a134e48497..545a82a9fb 100644 --- a/http/scraper_service_test.go +++ b/http/scraper_service_test.go @@ -15,7 +15,9 @@ import ( platcontext "github.com/influxdata/influxdb/v2/context" httpMock "github.com/influxdata/influxdb/v2/http/mock" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" + "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/mock" + "github.com/influxdata/influxdb/v2/tenant" platformtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -814,7 +816,12 @@ func TestService_handlePatchScraperTarget(t *testing.T) { func initScraperService(f platformtesting.TargetFields, t *testing.T) (influxdb.ScraperTargetStoreService, string, func()) { t.Helper() - svc := newInMemKVSVC(t) + + store := NewTestInmemStore(t) + tenantStore := tenant.NewStore(store) + tenantService := tenant.NewService(tenantStore) + + svc := kv.NewService(zaptest.NewLogger(t), store, tenantService) svc.IDGenerator = f.IDGenerator ctx := context.Background() @@ -825,15 +832,17 @@ func initScraperService(f platformtesting.TargetFields, t *testing.T) (influxdb. } for _, o := range f.Organizations { - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate orgs") - } + mock.SetIDForFunc(&tenantStore.OrgIDGen, o.ID, func() { + if err := tenantService.CreateOrganization(ctx, o); err != nil { + t.Fatalf("failed to populate orgs") + } + }) } scraperBackend := NewMockScraperBackend(t) scraperBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) scraperBackend.ScraperStorageService = svc - scraperBackend.OrganizationService = svc + scraperBackend.OrganizationService = tenantService scraperBackend.BucketService = &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { return &influxdb.Bucket{ @@ -856,8 +865,8 @@ func initScraperService(f platformtesting.TargetFields, t *testing.T) (influxdb. influxdb.OrganizationService ScraperService }{ - UserResourceMappingService: svc, - OrganizationService: svc, + UserResourceMappingService: tenantService, + OrganizationService: tenantService, ScraperService: ScraperService{ Token: "tok", Addr: server.URL, diff --git a/http/source_service.go b/http/source_service.go index 1753a5230d..7daeb8eb05 100644 --- a/http/source_service.go +++ b/http/source_service.go @@ -5,12 +5,13 @@ import ( "encoding/json" "fmt" "net/http" + "time" "github.com/influxdata/flux" "github.com/influxdata/flux/csv" "github.com/influxdata/flux/lang" "github.com/influxdata/httprouter" - platform "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/pkg/httpc" "github.com/influxdata/influxdb/v2/query" "github.com/influxdata/influxdb/v2/query/influxql" @@ -22,15 +23,15 @@ const ( ) type sourceResponse struct { - *platform.Source + *influxdb.Source Links map[string]interface{} `json:"links"` } -func newSourceResponse(s *platform.Source) *sourceResponse { +func newSourceResponse(s *influxdb.Source) *sourceResponse { s.Password = "" s.SharedSecret = "" - if s.Type == platform.SelfSourceType { + if s.Type == influxdb.SelfSourceType { return &sourceResponse{ Source: s, Links: map[string]interface{}{ @@ -58,7 +59,7 @@ type sourcesResponse struct { Links map[string]interface{} `json:"links"` } -func newSourcesResponse(srcs []*platform.Source) *sourcesResponse { +func newSourcesResponse(srcs []*influxdb.Source) *sourcesResponse { res := &sourcesResponse{ Links: map[string]interface{}{ "self": prefixSources, @@ -76,13 +77,13 @@ func newSourcesResponse(srcs []*platform.Source) *sourcesResponse { // SourceBackend is all services and associated parameters required to construct // the SourceHandler. type SourceBackend struct { - platform.HTTPErrorHandler + influxdb.HTTPErrorHandler log *zap.Logger - SourceService platform.SourceService - LabelService platform.LabelService - BucketService platform.BucketService - NewQueryService func(s *platform.Source) (query.ProxyQueryService, error) + SourceService influxdb.SourceService + LabelService influxdb.LabelService + BucketService influxdb.BucketService + NewQueryService func(s *influxdb.Source) (query.ProxyQueryService, error) } // NewSourceBackend returns a new instance of SourceBackend. @@ -101,15 +102,15 @@ func NewSourceBackend(log *zap.Logger, b *APIBackend) *SourceBackend { // SourceHandler is a handler for sources type SourceHandler struct { *httprouter.Router - platform.HTTPErrorHandler + influxdb.HTTPErrorHandler log *zap.Logger - SourceService platform.SourceService - LabelService platform.LabelService - BucketService platform.BucketService + SourceService influxdb.SourceService + LabelService influxdb.LabelService + BucketService influxdb.BucketService // TODO(desa): this was done so in order to remove an import cycle and to allow // for http mocking. - NewQueryService func(s *platform.Source) (query.ProxyQueryService, error) + NewQueryService func(s *influxdb.Source) (query.ProxyQueryService, error) } // NewSourceHandler returns a new instance of SourceHandler. @@ -147,7 +148,7 @@ func decodeSourceQueryRequest(r *http.Request) (*query.ProxyRequest, error) { DB string `json:"db"` RP string `json:"rp"` Cluster string `json:"cluster"` - OrganizationID platform.ID `json:"organizationID"` + OrganizationID influxdb.ID `json:"organizationID"` // TODO(desa): support influxql dialect Dialect csv.Dialect `json:"dialect"` }{} @@ -263,6 +264,149 @@ func decodeGetSourceBucketsRequest(ctx context.Context, r *http.Request) (*getSo }, nil } +type getBucketsRequest struct { + filter influxdb.BucketFilter + opts influxdb.FindOptions +} + +func decodeGetBucketsRequest(r *http.Request) (*getBucketsRequest, error) { + qp := r.URL.Query() + req := &getBucketsRequest{} + + opts, err := influxdb.DecodeFindOptions(r) + if err != nil { + return nil, err + } + + req.opts = *opts + + if orgID := qp.Get("orgID"); orgID != "" { + id, err := influxdb.IDFromString(orgID) + if err != nil { + return nil, err + } + req.filter.OrganizationID = id + } + + if org := qp.Get("org"); org != "" { + req.filter.Org = &org + } + + if name := qp.Get("name"); name != "" { + req.filter.Name = &name + } + + if bucketID := qp.Get("id"); bucketID != "" { + id, err := influxdb.IDFromString(bucketID) + if err != nil { + return nil, err + } + req.filter.ID = id + } + + return req, nil +} + +type bucketResponse struct { + bucket + Links map[string]string `json:"links"` + Labels []influxdb.Label `json:"labels"` +} + +type bucket struct { + ID influxdb.ID `json:"id,omitempty"` + OrgID influxdb.ID `json:"orgID,omitempty"` + Type string `json:"type"` + Description string `json:"description,omitempty"` + Name string `json:"name"` + RetentionPolicyName string `json:"rp,omitempty"` // This to support v1 sources + RetentionRules []retentionRule `json:"retentionRules"` + influxdb.CRUDLog +} + +func newBucket(pb *influxdb.Bucket) *bucket { + if pb == nil { + return nil + } + + rules := []retentionRule{} + rp := int64(pb.RetentionPeriod.Round(time.Second) / time.Second) + if rp > 0 { + rules = append(rules, retentionRule{ + Type: "expire", + EverySeconds: rp, + }) + } + + return &bucket{ + ID: pb.ID, + OrgID: pb.OrgID, + Type: pb.Type.String(), + Name: pb.Name, + Description: pb.Description, + RetentionPolicyName: pb.RetentionPolicyName, + RetentionRules: rules, + CRUDLog: pb.CRUDLog, + } +} + +// retentionRule is the retention rule action for a bucket. +type retentionRule struct { + Type string `json:"type"` + EverySeconds int64 `json:"everySeconds"` +} + +func (rr *retentionRule) RetentionPeriod() (time.Duration, error) { + t := time.Duration(rr.EverySeconds) * time.Second + if t < time.Second { + return t, &influxdb.Error{ + Code: influxdb.EUnprocessableEntity, + Msg: "expiration seconds must be greater than or equal to one second", + } + } + + return t, nil +} + +func NewBucketResponse(b *influxdb.Bucket, labels []*influxdb.Label) *bucketResponse { + res := &bucketResponse{ + Links: map[string]string{ + "labels": fmt.Sprintf("/api/v2/buckets/%s/labels", b.ID), + "logs": fmt.Sprintf("/api/v2/buckets/%s/logs", b.ID), + "members": fmt.Sprintf("/api/v2/buckets/%s/members", b.ID), + "org": fmt.Sprintf("/api/v2/orgs/%s", b.OrgID), + "owners": fmt.Sprintf("/api/v2/buckets/%s/owners", b.ID), + "self": fmt.Sprintf("/api/v2/buckets/%s", b.ID), + "write": fmt.Sprintf("/api/v2/write?org=%s&bucket=%s", b.OrgID, b.ID), + }, + bucket: *newBucket(b), + Labels: []influxdb.Label{}, + } + + for _, l := range labels { + res.Labels = append(res.Labels, *l) + } + + return res +} + +type bucketsResponse struct { + Links *influxdb.PagingLinks `json:"links"` + Buckets []*bucketResponse `json:"buckets"` +} + +func newBucketsResponse(ctx context.Context, opts influxdb.FindOptions, f influxdb.BucketFilter, bs []*influxdb.Bucket, labelService influxdb.LabelService) *bucketsResponse { + rs := make([]*bucketResponse, 0, len(bs)) + for _, b := range bs { + labels, _ := labelService.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ResourceID: b.ID, ResourceType: influxdb.BucketsResourceType}) + rs = append(rs, NewBucketResponse(b, labels)) + } + return &bucketsResponse{ + Links: influxdb.NewPagingLinks(prefixBuckets, opts, f, len(bs)), + Buckets: rs, + } +} + // handlePostSource is the HTTP handler for the POST /api/v2/sources route. func (h *SourceHandler) handlePostSource(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -286,11 +430,11 @@ func (h *SourceHandler) handlePostSource(w http.ResponseWriter, r *http.Request) } type postSourceRequest struct { - Source *platform.Source + Source *influxdb.Source } func decodePostSourceRequest(ctx context.Context, r *http.Request) (*postSourceRequest, error) { - b := &platform.Source{} + b := &influxdb.Source{} if err := json.NewDecoder(r.Body).Decode(b); err != nil { return nil, err } @@ -349,20 +493,20 @@ func (h *SourceHandler) handleGetSourceHealth(w http.ResponseWriter, r *http.Req } type getSourceRequest struct { - SourceID platform.ID + SourceID influxdb.ID } func decodeGetSourceRequest(ctx context.Context, r *http.Request) (*getSourceRequest, error) { params := httprouter.ParamsFromContext(ctx) id := params.ByName("id") if id == "" { - return nil, &platform.Error{ - Code: platform.EInvalid, + return nil, &influxdb.Error{ + Code: influxdb.EInvalid, Msg: "url missing id", } } - var i platform.ID + var i influxdb.ID if err := i.DecodeFromString(id); err != nil { return nil, err } @@ -392,20 +536,20 @@ func (h *SourceHandler) handleDeleteSource(w http.ResponseWriter, r *http.Reques } type deleteSourceRequest struct { - SourceID platform.ID + SourceID influxdb.ID } func decodeDeleteSourceRequest(ctx context.Context, r *http.Request) (*deleteSourceRequest, error) { params := httprouter.ParamsFromContext(ctx) id := params.ByName("id") if id == "" { - return nil, &platform.Error{ - Code: platform.EInvalid, + return nil, &influxdb.Error{ + Code: influxdb.EInvalid, Msg: "url missing id", } } - var i platform.ID + var i influxdb.ID if err := i.DecodeFromString(id); err != nil { return nil, err } @@ -441,7 +585,7 @@ func (h *SourceHandler) handleGetSources(w http.ResponseWriter, r *http.Request) } type getSourcesRequest struct { - findOptions platform.FindOptions + findOptions influxdb.FindOptions } func decodeGetSourcesRequest(ctx context.Context, r *http.Request) (*getSourcesRequest, error) { @@ -472,26 +616,26 @@ func (h *SourceHandler) handlePatchSource(w http.ResponseWriter, r *http.Request } type patchSourceRequest struct { - Update platform.SourceUpdate - SourceID platform.ID + Update influxdb.SourceUpdate + SourceID influxdb.ID } func decodePatchSourceRequest(ctx context.Context, r *http.Request) (*patchSourceRequest, error) { params := httprouter.ParamsFromContext(ctx) id := params.ByName("id") if id == "" { - return nil, &platform.Error{ - Code: platform.EInvalid, + return nil, &influxdb.Error{ + Code: influxdb.EInvalid, Msg: "url missing id", } } - var i platform.ID + var i influxdb.ID if err := i.DecodeFromString(id); err != nil { return nil, err } - var upd platform.SourceUpdate + var upd influxdb.SourceUpdate if err := json.NewDecoder(r.Body).Decode(&upd); err != nil { return nil, err } @@ -508,8 +652,8 @@ type SourceService struct { } // FindSourceByID returns a single source by ID. -func (s *SourceService) FindSourceByID(ctx context.Context, id platform.ID) (*platform.Source, error) { - var b platform.Source +func (s *SourceService) FindSourceByID(ctx context.Context, id influxdb.ID) (*influxdb.Source, error) { + var b influxdb.Source err := s.Client. Get(prefixSources, id.String()). DecodeJSON(&b). @@ -522,8 +666,8 @@ func (s *SourceService) FindSourceByID(ctx context.Context, id platform.ID) (*pl // FindSources returns a list of sources that match filter and the total count of matching sources. // Additional options provide pagination & sorting. -func (s *SourceService) FindSources(ctx context.Context, opt platform.FindOptions) ([]*platform.Source, int, error) { - var bs []*platform.Source +func (s *SourceService) FindSources(ctx context.Context, opt influxdb.FindOptions) ([]*influxdb.Source, int, error) { + var bs []*influxdb.Source err := s.Client. Get(prefixSources). DecodeJSON(&bs). @@ -536,7 +680,7 @@ func (s *SourceService) FindSources(ctx context.Context, opt platform.FindOption } // CreateSource creates a new source and sets b.ID with the new identifier. -func (s *SourceService) CreateSource(ctx context.Context, b *platform.Source) error { +func (s *SourceService) CreateSource(ctx context.Context, b *influxdb.Source) error { return s.Client. PostJSON(b, prefixSources). DecodeJSON(b). @@ -545,8 +689,8 @@ func (s *SourceService) CreateSource(ctx context.Context, b *platform.Source) er // UpdateSource updates a single source with changeset. // Returns the new source state after update. -func (s *SourceService) UpdateSource(ctx context.Context, id platform.ID, upd platform.SourceUpdate) (*platform.Source, error) { - var b platform.Source +func (s *SourceService) UpdateSource(ctx context.Context, id influxdb.ID, upd influxdb.SourceUpdate) (*influxdb.Source, error) { + var b influxdb.Source err := s.Client. PatchJSON(upd, prefixSources, id.String()). DecodeJSON(&b). @@ -558,7 +702,7 @@ func (s *SourceService) UpdateSource(ctx context.Context, id platform.ID, upd pl } // DeleteSource removes a source by ID. -func (s *SourceService) DeleteSource(ctx context.Context, id platform.ID) error { +func (s *SourceService) DeleteSource(ctx context.Context, id influxdb.ID) error { return s.Client. Delete(prefixSources, id.String()). StatusFn(func(resp *http.Response) error { diff --git a/http/task_service_test.go b/http/task_service_test.go index 3ed1234724..b4476a9601 100644 --- a/http/task_service_test.go +++ b/http/task_service_test.go @@ -18,7 +18,6 @@ import ( "github.com/influxdata/influxdb/v2/authorization" pcontext "github.com/influxdata/influxdb/v2/context" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" - "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/label" "github.com/influxdata/influxdb/v2/mock" _ "github.com/influxdata/influxdb/v2/fluxinit/static" @@ -31,6 +30,9 @@ import ( // NewMockTaskBackend returns a TaskBackend with mock services. func NewMockTaskBackend(t *testing.T) *TaskBackend { t.Helper() + store := NewTestInmemStore(t) + tenantService := tenant.NewService(tenant.NewStore(store)) + return &TaskBackend{ log: zaptest.NewLogger(t).With(zap.String("handler", "task")), @@ -60,7 +62,7 @@ func NewMockTaskBackend(t *testing.T) *TaskBackend { return org, nil }, }, - UserResourceMappingService: newInMemKVSVC(t), + UserResourceMappingService: tenantService, LabelService: mock.NewLabelService(), UserService: mock.NewUserService(), } @@ -819,14 +821,19 @@ func TestTaskHandler_handleGetRuns(t *testing.T) { func TestTaskHandler_NotFoundStatus(t *testing.T) { // Ensure that the HTTP handlers return 404s for missing resources, and OKs for matching. - im := newInMemKVSVC(t) + store := NewTestInmemStore(t) + tenantService := tenant.NewService(tenant.NewStore(store)) + + labelStore, _ := label.NewStore(store) + labelService := label.NewService(labelStore) + taskBackend := NewMockTaskBackend(t) taskBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) h := NewTaskHandler(zaptest.NewLogger(t), taskBackend) - h.UserResourceMappingService = im - h.LabelService = im - h.UserService = im - h.OrganizationService = im + h.UserResourceMappingService = tenantService + h.LabelService = labelService + h.UserService = tenantService + h.OrganizationService = tenantService o := influxdb.Organization{Name: "o"} ctx := context.Background() @@ -1308,11 +1315,22 @@ func TestTaskHandler_Sessions(t *testing.T) { t.Skip("rework these") // Common setup to get a working base for using tasks. st := NewTestInmemStore(t) - i := kv.NewService(zaptest.NewLogger(t), st) tStore := tenant.NewStore(st) tSvc := tenant.NewService(tStore) + labelStore, err := label.NewStore(st) + if err != nil { + t.Fatal(err) + } + labelService := label.NewService(labelStore) + + authStore, err := authorization.NewStore(st) + if err != nil { + t.Fatal(err) + } + authService := authorization.NewService(authStore, tSvc) + ctx := context.Background() // Set up user and org. @@ -1357,10 +1375,10 @@ func TestTaskHandler_Sessions(t *testing.T) { log: zaptest.NewLogger(t), TaskService: ts, - AuthorizationService: i, + AuthorizationService: authService, OrganizationService: tSvc, UserResourceMappingService: tSvc, - LabelService: i, + LabelService: labelService, UserService: tSvc, BucketService: tSvc, }) @@ -1369,7 +1387,7 @@ func TestTaskHandler_Sessions(t *testing.T) { t.Run("get runs for a task", func(t *testing.T) { // Unique authorization to associate with our fake task. taskAuth := &influxdb.Authorization{OrgID: o.ID, UserID: u.ID} - if err := i.CreateAuthorization(ctx, taskAuth); err != nil { + if err := authService.CreateAuthorization(ctx, taskAuth); err != nil { t.Fatal(err) } @@ -1434,7 +1452,7 @@ func TestTaskHandler_Sessions(t *testing.T) { // Other user without permissions on the task or authorization should be disallowed. otherUser := &influxdb.User{Name: "other-" + t.Name()} - if err := i.CreateUser(ctx, otherUser); err != nil { + if err := tSvc.CreateUser(ctx, otherUser); err != nil { t.Fatal(err) } @@ -1461,7 +1479,7 @@ func TestTaskHandler_Sessions(t *testing.T) { t.Run("get single run for a task", func(t *testing.T) { // Unique authorization to associate with our fake task. taskAuth := &influxdb.Authorization{OrgID: o.ID, UserID: u.ID} - if err := i.CreateAuthorization(ctx, taskAuth); err != nil { + if err := authService.CreateAuthorization(ctx, taskAuth); err != nil { t.Fatal(err) } @@ -1528,7 +1546,7 @@ func TestTaskHandler_Sessions(t *testing.T) { // Other user without permissions on the task or authorization should be disallowed. otherUser := &influxdb.User{Name: "other-" + t.Name()} - if err := i.CreateUser(ctx, otherUser); err != nil { + if err := tSvc.CreateUser(ctx, otherUser); err != nil { t.Fatal(err) } @@ -1555,7 +1573,7 @@ func TestTaskHandler_Sessions(t *testing.T) { t.Run("get logs for a run", func(t *testing.T) { // Unique authorization to associate with our fake task. taskAuth := &influxdb.Authorization{OrgID: o.ID, UserID: u.ID} - if err := i.CreateAuthorization(ctx, taskAuth); err != nil { + if err := authService.CreateAuthorization(ctx, taskAuth); err != nil { t.Fatal(err) } @@ -1623,7 +1641,7 @@ func TestTaskHandler_Sessions(t *testing.T) { // Other user without permissions on the task or authorization should be disallowed. otherUser := &influxdb.User{Name: "other-" + t.Name()} - if err := i.CreateUser(ctx, otherUser); err != nil { + if err := tSvc.CreateUser(ctx, otherUser); err != nil { t.Fatal(err) } @@ -1650,7 +1668,7 @@ func TestTaskHandler_Sessions(t *testing.T) { t.Run("retry a run", func(t *testing.T) { // Unique authorization to associate with our fake task. taskAuth := &influxdb.Authorization{OrgID: o.ID, UserID: u.ID} - if err := i.CreateAuthorization(ctx, taskAuth); err != nil { + if err := authService.CreateAuthorization(ctx, taskAuth); err != nil { t.Fatal(err) } @@ -1717,7 +1735,7 @@ func TestTaskHandler_Sessions(t *testing.T) { // Other user without permissions on the task or authorization should be disallowed. otherUser := &influxdb.User{Name: "other-" + t.Name()} - if err := i.CreateUser(ctx, otherUser); err != nil { + if err := tSvc.CreateUser(ctx, otherUser); err != nil { t.Fatal(err) } diff --git a/http/usage_service.go b/http/usage_service.go deleted file mode 100644 index c8fec2f789..0000000000 --- a/http/usage_service.go +++ /dev/null @@ -1,129 +0,0 @@ -package http - -import ( - "context" - "errors" - "net/http" - "time" - - "github.com/influxdata/httprouter" - platform "github.com/influxdata/influxdb/v2" - "go.uber.org/zap" -) - -// UsageHandler represents an HTTP API handler for usages. -type UsageHandler struct { - *httprouter.Router - platform.HTTPErrorHandler - log *zap.Logger - - UsageService platform.UsageService -} - -// NewUsageHandler returns a new instance of UsageHandler. -func NewUsageHandler(log *zap.Logger, he platform.HTTPErrorHandler) *UsageHandler { - h := &UsageHandler{ - Router: NewRouter(he), - log: log, - } - - h.HandlerFunc("GET", "/api/v2/usage", h.handleGetUsage) - return h -} - -// handleGetUsage is the HTTP handler for the GET /api/v2/usage route. -func (h *UsageHandler) handleGetUsage(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - req, err := decodeGetUsageRequest(ctx, r) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - b, err := h.UsageService.GetUsage(ctx, req.filter) - if err != nil { - h.HandleHTTPError(ctx, err, w) - return - } - - if err := encodeResponse(ctx, w, http.StatusOK, b); err != nil { - logEncodingError(h.log, r, err) - return - } -} - -type getUsageRequest struct { - filter platform.UsageFilter -} - -func decodeGetUsageRequest(ctx context.Context, r *http.Request) (*getUsageRequest, error) { - req := &getUsageRequest{} - qp := r.URL.Query() - - orgID := qp.Get("orgID") - if orgID != "" { - var id platform.ID - if err := (&id).DecodeFromString(orgID); err != nil { - return nil, err - } - req.filter.OrgID = &id - } - - bucketID := qp.Get("bucketID") - if bucketID != "" { - var id platform.ID - if err := (&id).DecodeFromString(bucketID); err != nil { - return nil, err - } - req.filter.BucketID = &id - } - - start := qp.Get("start") - stop := qp.Get("stop") - - if start == "" && stop != "" { - return nil, errors.New("start query param required") - } - if stop == "" && start != "" { - return nil, errors.New("stop query param required") - } - - if start == "" && stop == "" { - now := time.Now() - month := roundToMonth(now) - - req.filter.Range = &platform.Timespan{ - Start: month, - Stop: now, - } - } - - if start != "" && stop != "" { - startTime, err := time.Parse(time.RFC3339, start) - if err != nil { - return nil, err - } - - stopTime, err := time.Parse(time.RFC3339, stop) - if err != nil { - return nil, err - } - - req.filter.Range = &platform.Timespan{ - Start: startTime, - Stop: stopTime, - } - } - - return req, nil -} - -func roundToMonth(t time.Time) time.Time { - h, m, s := t.Clock() - d := t.Day() - - delta := (time.Duration(d) * 24 * time.Hour) + time.Duration(h)*time.Hour + time.Duration(m)*time.Minute + time.Duration(s)*time.Second - - return t.Add(-1 * delta).Round(time.Minute) -} diff --git a/http/user_test.go b/http/user_test.go index 4149e2f05d..12777ae083 100644 --- a/http/user_test.go +++ b/http/user_test.go @@ -12,6 +12,7 @@ import ( kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" "github.com/influxdata/influxdb/v2/mock" "github.com/influxdata/influxdb/v2/pkg/testttp" + "github.com/influxdata/influxdb/v2/tenant" platformtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -29,19 +30,22 @@ func NewMockUserBackend(t *testing.T) *UserBackend { func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserService, string, func()) { t.Helper() - svc := newInMemKVSVC(t) - svc.IDGenerator = f.IDGenerator + + store := NewTestInmemStore(t) + tenantStore := tenant.NewStore(store) + tenantStore.IDGen = f.IDGenerator + tenantService := tenant.NewService(tenantStore) ctx := context.Background() for _, u := range f.Users { - if err := svc.PutUser(ctx, u); err != nil { + if err := tenantService.CreateUser(ctx, u); err != nil { t.Fatalf("failed to populate users") } } userBackend := NewMockUserBackend(t) userBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) - userBackend.UserService = svc + userBackend.UserService = tenantService handler := NewUserHandler(zaptest.NewLogger(t), userBackend) server := httptest.NewServer(handler) diff --git a/http/variable_test.go b/http/variable_test.go index 0d93eda68d..f38f371a78 100644 --- a/http/variable_test.go +++ b/http/variable_test.go @@ -14,7 +14,9 @@ import ( "github.com/influxdata/httprouter" platform "github.com/influxdata/influxdb/v2" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" + "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/mock" + "github.com/influxdata/influxdb/v2/tenant" itesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -992,7 +994,10 @@ func TestService_handlePostVariableLabel(t *testing.T) { } func initVariableService(f itesting.VariableFields, t *testing.T) (platform.VariableService, string, func()) { - svc := newInMemKVSVC(t) + store := NewTestInmemStore(t) + tenantService := tenant.NewService(tenant.NewStore(store)) + + svc := kv.NewService(zaptest.NewLogger(t), store, tenantService) svc.IDGenerator = f.IDGenerator svc.TimeGenerator = f.TimeGenerator diff --git a/kv/auth.go b/kv/auth.go deleted file mode 100644 index b8320cfdef..0000000000 --- a/kv/auth.go +++ /dev/null @@ -1,561 +0,0 @@ -package kv - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/buger/jsonparser" - influxdb "github.com/influxdata/influxdb/v2" - jsonp "github.com/influxdata/influxdb/v2/pkg/jsonparser" -) - -var ( - authBucket = []byte("authorizationsv1") - authIndex = []byte("authorizationindexv1") -) - -var _ influxdb.AuthorizationService = (*Service)(nil) - -// FindAuthorizationByID retrieves a authorization by id. -func (s *Service) FindAuthorizationByID(ctx context.Context, id influxdb.ID) (*influxdb.Authorization, error) { - var a *influxdb.Authorization - err := s.kv.View(ctx, func(tx Tx) error { - auth, err := s.findAuthorizationByID(ctx, tx, id) - if err != nil { - return err - } - - a = auth - return nil - }) - - if err != nil { - return nil, err - } - - return a, nil -} - -func (s *Service) findAuthorizationByID(ctx context.Context, tx Tx, id influxdb.ID) (*influxdb.Authorization, error) { - encodedID, err := id.Encode() - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - b, err := tx.Bucket(authBucket) - if err != nil { - return nil, err - } - - v, err := b.Get(encodedID) - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "authorization not found", - } - } - - if err != nil { - return nil, err - } - - a := &influxdb.Authorization{} - if err := decodeAuthorization(v, a); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - return a, nil -} - -// FindAuthorizationByToken returns a authorization by token for a particular authorization. -func (s *Service) FindAuthorizationByToken(ctx context.Context, n string) (*influxdb.Authorization, error) { - var a *influxdb.Authorization - err := s.kv.View(ctx, func(tx Tx) error { - auth, err := s.findAuthorizationByToken(ctx, tx, n) - if err != nil { - return err - } - - a = auth - - return nil - }) - - if err != nil { - return nil, err - } - - return a, nil -} - -func (s *Service) findAuthorizationByToken(ctx context.Context, tx Tx, n string) (*influxdb.Authorization, error) { - idx, err := authIndexBucket(tx) - if err != nil { - return nil, err - } - - a, err := idx.Get(authIndexKey(n)) - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "authorization not found", - } - } - - var id influxdb.ID - if err := id.Decode(a); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - return s.findAuthorizationByID(ctx, tx, id) -} - -func authorizationsPredicateFn(f influxdb.AuthorizationFilter) CursorPredicateFunc { - // if any errors occur reading the JSON data, the predicate will always return true - // to ensure the value is included and handled higher up. - - if f.ID != nil { - exp := *f.ID - return func(_, value []byte) bool { - got, err := jsonp.GetID(value, "id") - if err != nil { - return true - } - return got == exp - } - } - - if f.Token != nil { - exp := *f.Token - return func(_, value []byte) bool { - // it is assumed that token never has escaped string data - got, _, _, err := jsonparser.Get(value, "token") - if err != nil { - return true - } - return string(got) == exp - } - } - - var pred CursorPredicateFunc - if f.OrgID != nil { - exp := *f.OrgID - pred = func(_, value []byte) bool { - got, err := jsonp.GetID(value, "orgID") - if err != nil { - return true - } - - return got == exp - } - } - - if f.UserID != nil { - exp := *f.UserID - prevFn := pred - pred = func(key, value []byte) bool { - prev := prevFn == nil || prevFn(key, value) - got, exists, err := jsonp.GetOptionalID(value, "userID") - return prev && ((exp == got && exists) || err != nil) - } - } - - return pred -} - -func filterAuthorizationsFn(filter influxdb.AuthorizationFilter) func(a *influxdb.Authorization) bool { - if filter.ID != nil { - return func(a *influxdb.Authorization) bool { - return a.ID == *filter.ID - } - } - - if filter.Token != nil { - return func(a *influxdb.Authorization) bool { - return a.Token == *filter.Token - } - } - - // Filter by org and user - if filter.OrgID != nil && filter.UserID != nil { - return func(a *influxdb.Authorization) bool { - return a.OrgID == *filter.OrgID && a.UserID == *filter.UserID - } - } - - if filter.OrgID != nil { - return func(a *influxdb.Authorization) bool { - return a.OrgID == *filter.OrgID - } - } - - if filter.UserID != nil { - return func(a *influxdb.Authorization) bool { - return a.UserID == *filter.UserID - } - } - - return func(a *influxdb.Authorization) bool { return true } -} - -// FindAuthorizations retrieves all authorizations that match an arbitrary authorization filter. -// Filters using ID, or Token should be efficient. -// Other filters will do a linear scan across all authorizations searching for a match. -func (s *Service) FindAuthorizations(ctx context.Context, filter influxdb.AuthorizationFilter, opt ...influxdb.FindOptions) ([]*influxdb.Authorization, int, error) { - if filter.ID != nil { - a, err := s.FindAuthorizationByID(ctx, *filter.ID) - if err != nil { - return nil, 0, &influxdb.Error{ - Err: err, - } - } - - return []*influxdb.Authorization{a}, 1, nil - } - - if filter.Token != nil { - a, err := s.FindAuthorizationByToken(ctx, *filter.Token) - if err != nil { - return nil, 0, &influxdb.Error{ - Err: err, - } - } - - return []*influxdb.Authorization{a}, 1, nil - } - - as := []*influxdb.Authorization{} - err := s.kv.View(ctx, func(tx Tx) error { - auths, err := s.findAuthorizations(ctx, tx, filter) - if err != nil { - return err - } - as = auths - return nil - }) - - if err != nil { - return nil, 0, &influxdb.Error{ - Err: err, - } - } - - return as, len(as), nil -} - -func (s *Service) findAuthorizations(ctx context.Context, tx Tx, f influxdb.AuthorizationFilter) ([]*influxdb.Authorization, error) { - // If the users name was provided, look up user by ID first - if f.User != nil { - u, err := s.findUserByName(ctx, tx, *f.User) - if err != nil { - return nil, err - } - f.UserID = &u.ID - } - - if f.Org != nil { - o, err := s.findOrganizationByName(ctx, tx, *f.Org) - if err != nil { - return nil, err - } - f.OrgID = &o.ID - } - - var as []*influxdb.Authorization - pred := authorizationsPredicateFn(f) - filterFn := filterAuthorizationsFn(f) - err := s.forEachAuthorization(ctx, tx, pred, func(a *influxdb.Authorization) bool { - if filterFn(a) { - as = append(as, a) - } - return true - }) - if err != nil { - return nil, err - } - - return as, nil -} - -// CreateAuthorization creates a influxdb authorization and sets b.ID, and b.UserID if not provided. -func (s *Service) CreateAuthorization(ctx context.Context, a *influxdb.Authorization) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.createAuthorization(ctx, tx, a) - }) -} - -// CreateAuthorizationTx is used when importing kv as a library -func (s *Service) CreateAuthorizationTx(ctx context.Context, tx Tx, a *influxdb.Authorization) error { - return s.createAuthorization(ctx, tx, a) -} - -func (s *Service) createAuthorization(ctx context.Context, tx Tx, a *influxdb.Authorization) error { - if err := a.Valid(); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - if _, err := s.findUserByID(ctx, tx, a.UserID); err != nil { - return influxdb.ErrUnableToCreateToken - } - - if _, err := s.findOrganizationByID(ctx, tx, a.OrgID); err != nil { - return influxdb.ErrUnableToCreateToken - } - - if err := s.uniqueAuthToken(ctx, tx, a); err != nil { - return err - } - - if a.Token == "" { - token, err := s.TokenGenerator.Token() - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - a.Token = token - } - - a.ID = s.IDGenerator.ID() - - now := s.TimeGenerator.Now() - a.SetCreatedAt(now) - a.SetUpdatedAt(now) - - if err := s.putAuthorization(ctx, tx, a); err != nil { - return err - } - - return nil -} - -// PutAuthorization will put a authorization without setting an ID. -func (s *Service) PutAuthorization(ctx context.Context, a *influxdb.Authorization) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.putAuthorization(ctx, tx, a) - }) -} - -func encodeAuthorization(a *influxdb.Authorization) ([]byte, error) { - switch a.Status { - case influxdb.Active, influxdb.Inactive: - case "": - a.Status = influxdb.Active - default: - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "unknown authorization status", - } - } - - return json.Marshal(a) -} - -func (s *Service) putAuthorization(ctx context.Context, tx Tx, a *influxdb.Authorization) error { - v, err := encodeAuthorization(a) - if err != nil { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - encodedID, err := a.ID.Encode() - if err != nil { - return &influxdb.Error{ - Code: influxdb.ENotFound, - Err: err, - } - } - - idx, err := authIndexBucket(tx) - if err != nil { - return err - } - - if err := idx.Put(authIndexKey(a.Token), encodedID); err != nil { - return &influxdb.Error{ - Code: influxdb.EInternal, - Err: err, - } - } - - b, err := tx.Bucket(authBucket) - if err != nil { - return err - } - - if err := b.Put(encodedID, v); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - return nil -} - -func authIndexKey(n string) []byte { - return []byte(n) -} - -func decodeAuthorization(b []byte, a *influxdb.Authorization) error { - if err := json.Unmarshal(b, a); err != nil { - return err - } - if a.Status == "" { - a.Status = influxdb.Active - } - return nil -} - -// forEachAuthorization will iterate through all authorizations while fn returns true. -func (s *Service) forEachAuthorization(ctx context.Context, tx Tx, pred CursorPredicateFunc, fn func(*influxdb.Authorization) bool) error { - b, err := tx.Bucket(authBucket) - if err != nil { - return err - } - - var cur Cursor - if pred != nil { - cur, err = b.Cursor(WithCursorHintPredicate(pred)) - } else { - cur, err = b.Cursor() - } - if err != nil { - return err - } - - for k, v := cur.First(); k != nil; k, v = cur.Next() { - // preallocate Permissions to reduce multiple slice re-allocations - a := &influxdb.Authorization{ - Permissions: make([]influxdb.Permission, 64), - } - - if err := decodeAuthorization(v, a); err != nil { - return err - } - if !fn(a) { - break - } - } - - return nil -} - -// DeleteAuthorization deletes a authorization and prunes it from the index. -func (s *Service) DeleteAuthorization(ctx context.Context, id influxdb.ID) error { - return s.kv.Update(ctx, func(tx Tx) (err error) { - return s.deleteAuthorization(ctx, tx, id) - }) -} - -func (s *Service) deleteAuthorization(ctx context.Context, tx Tx, id influxdb.ID) error { - a, err := s.findAuthorizationByID(ctx, tx, id) - if err != nil { - return err - } - - idx, err := authIndexBucket(tx) - if err != nil { - return err - } - - if err := idx.Delete(authIndexKey(a.Token)); err != nil { - return &influxdb.Error{ - Err: err, - } - } - encodedID, err := id.Encode() - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - b, err := tx.Bucket(authBucket) - if err != nil { - return err - } - - if err := b.Delete(encodedID); err != nil { - return &influxdb.Error{ - Err: err, - } - } - return nil -} - -// UpdateAuthorization updates the status and description if available. -func (s *Service) UpdateAuthorization(ctx context.Context, id influxdb.ID, upd *influxdb.AuthorizationUpdate) (*influxdb.Authorization, error) { - var a *influxdb.Authorization - var err error - err = s.kv.Update(ctx, func(tx Tx) error { - a, err = s.updateAuthorization(ctx, tx, id, upd) - return err - }) - return a, err -} - -func (s *Service) updateAuthorization(ctx context.Context, tx Tx, id influxdb.ID, upd *influxdb.AuthorizationUpdate) (*influxdb.Authorization, error) { - a, err := s.findAuthorizationByID(ctx, tx, id) - if err != nil { - return nil, err - } - - if upd.Status != nil { - a.Status = *upd.Status - } - if upd.Description != nil { - a.Description = *upd.Description - } - - now := s.TimeGenerator.Now() - a.SetUpdatedAt(now) - - if err := s.putAuthorization(ctx, tx, a); err != nil { - return nil, err - } - - return a, nil -} - -func authIndexBucket(tx Tx) (Bucket, error) { - b, err := tx.Bucket([]byte(authIndex)) - if err != nil { - return nil, UnexpectedAuthIndexError(err) - } - - return b, nil -} - -// UnexpectedAuthIndexError is used when the error comes from an internal system. -func UnexpectedAuthIndexError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("unexpected error retrieving auth index; Err: %v", err), - Op: "kv/authIndex", - } -} - -func (s *Service) uniqueAuthToken(ctx context.Context, tx Tx, a *influxdb.Authorization) error { - err := s.unique(ctx, tx, authIndex, authIndexKey(a.Token)) - if err == NotUniqueError { - // by returning a generic error we are trying to hide when - // a token is non-unique. - return influxdb.ErrUnableToCreateToken - } - // otherwise, this is some sort of internal server error and we - // should provide some debugging information. - return err -} diff --git a/kv/auth_private_test.go b/kv/auth_private_test.go deleted file mode 100644 index 856b8e9b4a..0000000000 --- a/kv/auth_private_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package kv - -import ( - "encoding/json" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2" -) - -func mustMarshal(t testing.TB, v interface{}) []byte { - t.Helper() - d, err := json.Marshal(v) - if err != nil { - t.Fatal(err) - } - return d -} - -func Test_authorizationsPredicateFn(t *testing.T) { - t.Run("ID", func(t *testing.T) { - val := influxdb.ID(1) - f := influxdb.AuthorizationFilter{ID: &val} - fn := authorizationsPredicateFn(f) - - t.Run("does match", func(t *testing.T) { - a := &influxdb.Authorization{ID: val, OrgID: 2} - if got, exp := fn(nil, mustMarshal(t, a)), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("does not match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 2} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - }) - - t.Run("token", func(t *testing.T) { - val := "token_token" - f := influxdb.AuthorizationFilter{Token: &val} - fn := authorizationsPredicateFn(f) - - t.Run("does match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 2, Token: val} - if got, exp := fn(nil, mustMarshal(t, a)), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("does not match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 2, Token: "no_no_no"} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - }) - - t.Run("orgID", func(t *testing.T) { - val := influxdb.ID(1) - f := influxdb.AuthorizationFilter{OrgID: &val} - fn := authorizationsPredicateFn(f) - - t.Run("does match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: val} - if got, exp := fn(nil, mustMarshal(t, a)), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("does not match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 2} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - }) - - t.Run("userID", func(t *testing.T) { - val := influxdb.ID(1) - f := influxdb.AuthorizationFilter{UserID: &val} - fn := authorizationsPredicateFn(f) - - t.Run("does match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 5, UserID: val} - if got, exp := fn(nil, mustMarshal(t, a)), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("does not match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 5, UserID: 2} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("missing userID", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 5} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - }) - - t.Run("orgID and userID", func(t *testing.T) { - orgID := influxdb.ID(1) - userID := influxdb.ID(10) - f := influxdb.AuthorizationFilter{OrgID: &orgID, UserID: &userID} - fn := authorizationsPredicateFn(f) - - t.Run("does match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: orgID, UserID: userID} - if got, exp := fn(nil, mustMarshal(t, a)), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("org match user not match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: orgID, UserID: 11} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("org not match user match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 2, UserID: userID} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("org and user not match", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: 2, UserID: 11} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("org match user missing", func(t *testing.T) { - a := &influxdb.Authorization{ID: 10, OrgID: orgID} - if got, exp := fn(nil, mustMarshal(t, a)), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - }) -} diff --git a/kv/auth_test.go b/kv/auth_test.go deleted file mode 100644 index f9e5ada4df..0000000000 --- a/kv/auth_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltAuthorizationService(t *testing.T) { - influxdbtesting.AuthorizationService(initBoltAuthorizationService, t) -} - -func initBoltAuthorizationService(f influxdbtesting.AuthorizationFields, t *testing.T) (influxdb.AuthorizationService, string, func()) { - s, closeBolt, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new kv store: %v", err) - } - - svc, op, closeSvc := initAuthorizationService(s, f, t) - return svc, op, func() { - closeSvc() - closeBolt() - } -} - -func initAuthorizationService(s kv.SchemaStore, f influxdbtesting.AuthorizationFields, t *testing.T) (influxdb.AuthorizationService, string, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.IDGenerator = f.IDGenerator - svc.OrgIDs = f.OrgIDGenerator - svc.TokenGenerator = f.TokenGenerator - svc.TimeGenerator = f.TimeGenerator - - for _, u := range f.Users { - if err := svc.PutUser(ctx, u); err != nil { - t.Fatalf("failed to populate users") - } - } - - for _, o := range f.Orgs { - o.ID = svc.OrgIDs.ID() - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate orgs") - } - } - - for _, a := range f.Authorizations { - if err := svc.PutAuthorization(ctx, a); err != nil { - t.Fatalf("failed to populate authorizations %s", err) - } - } - - return svc, kv.OpPrefix, func() { - for _, u := range f.Users { - if err := svc.DeleteUser(ctx, u.ID); err != nil { - t.Logf("failed to remove user: %v", err) - } - } - - for _, o := range f.Orgs { - if err := svc.DeleteOrganization(ctx, o.ID); err != nil { - t.Logf("failed to remove org: %v", err) - } - } - - for _, a := range f.Authorizations { - if err := svc.DeleteAuthorization(ctx, a.ID); err != nil { - t.Logf("failed to remove authorizations: %v", err) - } - } - } -} diff --git a/kv/bucket.go b/kv/bucket.go deleted file mode 100644 index f9385fc47a..0000000000 --- a/kv/bucket.go +++ /dev/null @@ -1,914 +0,0 @@ -package kv - -import ( - "context" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/influxdata/influxdb/v2" - icontext "github.com/influxdata/influxdb/v2/context" - "github.com/influxdata/influxdb/v2/kit/tracing" - "github.com/influxdata/influxdb/v2/resource" -) - -var ( - bucketBucket = []byte("bucketsv1") - bucketIndex = []byte("bucketindexv1") -) - -var _ influxdb.BucketService = (*Service)(nil) -var _ influxdb.BucketOperationLogService = (*Service)(nil) - -func (s *Service) bucketsBucket(tx Tx) (Bucket, error) { - b, err := tx.Bucket(bucketBucket) - if err != nil { - return nil, UnexpectedBucketError(err) - } - - return b, nil -} - -func (s *Service) bucketsIndexBucket(tx Tx) (Bucket, error) { - b, err := tx.Bucket(bucketIndex) - if err != nil { - return nil, UnexpectedBucketIndexError(err) - } - - return b, nil -} - -// FindBucketByID retrieves a bucket by id. -func (s *Service) FindBucketByID(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var b *influxdb.Bucket - var err error - - err = s.kv.View(ctx, func(tx Tx) error { - bkt, pe := s.findBucketByID(ctx, tx, id) - if pe != nil { - err = pe - return err - } - b = bkt - return nil - }) - - if err != nil { - return nil, err - } - - return b, nil -} - -func (s *Service) findBucketByID(ctx context.Context, tx Tx, id influxdb.ID) (*influxdb.Bucket, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var b influxdb.Bucket - - encodedID, err := id.Encode() - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - bkt, err := s.bucketsBucket(tx) - if err != nil { - return nil, err - } - - v, err := bkt.Get(encodedID) - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - } - - if err != nil { - return nil, err - } - - if err := json.Unmarshal(v, &b); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return &b, nil -} - -// FindBucketByName returns a bucket by name for a particular organization. -// TODO: have method for finding bucket using organization name and bucket name. -func (s *Service) FindBucketByName(ctx context.Context, orgID influxdb.ID, n string) (*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var b *influxdb.Bucket - err := s.kv.View(ctx, func(tx Tx) error { - bkt, pe := s.findBucketByName(ctx, tx, orgID, n) - if pe != nil { - return pe - } - - b = bkt - return nil - }) - - return b, err -} - -// CreateSystemBuckets for an organization -func (s *Service) CreateSystemBuckets(ctx context.Context, o *influxdb.Organization) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.createSystemBuckets(ctx, tx, o) - }) -} - -// createSystemBuckets creates the task and monitoring system buckets for an organization -func (s *Service) createSystemBuckets(ctx context.Context, tx Tx, o *influxdb.Organization) error { - tb := &influxdb.Bucket{ - OrgID: o.ID, - Type: influxdb.BucketTypeSystem, - Name: influxdb.TasksSystemBucketName, - RetentionPeriod: influxdb.TasksSystemBucketRetention, - Description: "System bucket for task logs", - } - - if err := s.createBucket(ctx, tx, tb); err != nil { - return err - } - - mb := &influxdb.Bucket{ - OrgID: o.ID, - Type: influxdb.BucketTypeSystem, - Name: influxdb.MonitoringSystemBucketName, - RetentionPeriod: influxdb.MonitoringSystemBucketRetention, - Description: "System bucket for monitoring logs", - } - - return s.createBucket(ctx, tx, mb) -} - -func (s *Service) findBucketByName(ctx context.Context, tx Tx, orgID influxdb.ID, n string) (*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - b := &influxdb.Bucket{ - OrgID: orgID, - Name: n, - } - key, err := bucketIndexKey(b) - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - idx, err := s.bucketsIndexBucket(tx) - if err != nil { - return nil, err - } - - buf, err := idx.Get(key) - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: fmt.Sprintf("bucket %q not found", n), - } - } - - if err != nil { - return nil, err - } - - var id influxdb.ID - if err := id.Decode(buf); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - return s.findBucketByID(ctx, tx, id) -} - -// FindBucket retrieves a bucket using an arbitrary bucket filter. -// Filters using ID, or OrganizationID and bucket Name should be efficient. -// Other filters will do a linear scan across buckets until it finds a match. -func (s *Service) FindBucket(ctx context.Context, filter influxdb.BucketFilter) (*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var b *influxdb.Bucket - var err error - - if filter.ID != nil { - b, err = s.FindBucketByID(ctx, *filter.ID) - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - return b, nil - } - - if filter.Name != nil && filter.OrganizationID != nil { - return s.FindBucketByName(ctx, *filter.OrganizationID, *filter.Name) - } - - err = s.kv.View(ctx, func(tx Tx) error { - if filter.Org != nil { - o, err := s.findOrganizationByName(ctx, tx, *filter.Org) - if err != nil { - return err - } - filter.OrganizationID = &o.ID - } - - filterFn := filterBucketsFn(filter) - return s.forEachBucket(ctx, tx, false, func(bkt *influxdb.Bucket) bool { - if filterFn(bkt) { - b = bkt - return false - } - return true - }) - }) - - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - if b == nil { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "bucket not found", - } - } - - return b, nil -} - -func filterBucketsFn(filter influxdb.BucketFilter) func(b *influxdb.Bucket) bool { - if filter.ID != nil { - return func(b *influxdb.Bucket) bool { - return b.ID == *filter.ID - } - } - - if filter.Name != nil && filter.OrganizationID != nil { - return func(b *influxdb.Bucket) bool { - return b.Name == *filter.Name && b.OrgID == *filter.OrganizationID - } - } - - if filter.Name != nil { - return func(b *influxdb.Bucket) bool { - return b.Name == *filter.Name - } - } - - if filter.OrganizationID != nil { - return func(b *influxdb.Bucket) bool { - return b.OrgID == *filter.OrganizationID - } - } - - return func(b *influxdb.Bucket) bool { return true } -} - -// FindBuckets retrieves all buckets that match an arbitrary bucket filter. -// Filters using ID, or OrganizationID and bucket Name should be efficient. -// Other filters will do a linear scan across all buckets searching for a match. -func (s *Service) FindBuckets(ctx context.Context, filter influxdb.BucketFilter, opts ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - if filter.ID != nil { - b, err := s.FindBucketByID(ctx, *filter.ID) - if err != nil { - return nil, 0, err - } - - return []*influxdb.Bucket{b}, 1, nil - } - - if filter.Name != nil && filter.OrganizationID != nil { - b, err := s.FindBucketByName(ctx, *filter.OrganizationID, *filter.Name) - if err != nil { - return nil, 0, err - } - - return []*influxdb.Bucket{b}, 1, nil - } - - bs := []*influxdb.Bucket{} - err := s.kv.View(ctx, func(tx Tx) error { - bkts, err := s.findBuckets(ctx, tx, filter, opts...) - if err != nil { - return err - } - - bs = bkts - - return nil - }) - - if err != nil { - return nil, 0, err - } - - // Don't append system buckets if Name is set. Users who don't have real - // system buckets won't get mocked buckets if they query for a bucket by name - // without the orgID, but this is a vanishing small number of users and has - // limited utility anyways. Can be removed once mock system code is ripped out. - if filter.Name != nil { - return bs, len(bs), nil - } - - return bs, len(bs), nil -} - -func (s *Service) findBuckets(ctx context.Context, tx Tx, filter influxdb.BucketFilter, opts ...influxdb.FindOptions) ([]*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - bs := []*influxdb.Bucket{} - if filter.Org != nil { - o, err := s.findOrganizationByName(ctx, tx, *filter.Org) - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - filter.OrganizationID = &o.ID - } - - var ( - offset, limit, count int - descending bool - ) - - after := func(*influxdb.Bucket) bool { - return true - } - - if len(opts) > 0 { - offset = opts[0].Offset - limit = opts[0].Limit - descending = opts[0].Descending - if opts[0].After != nil { - after = func(b *influxdb.Bucket) bool { - if descending { - return b.ID < *opts[0].After - } - - return b.ID > *opts[0].After - } - } - } - - filterFn := filterBucketsFn(filter) - err := s.forEachBucket(ctx, tx, descending, func(b *influxdb.Bucket) bool { - if filterFn(b) { - if count >= offset && after(b) { - bs = append(bs, b) - } - count++ - } - - if limit > 0 && len(bs) >= limit { - return false - } - - return true - }) - - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return bs, nil -} - -// CreateBucket creates a influxdb bucket and sets b.ID. -func (s *Service) CreateBucket(ctx context.Context, b *influxdb.Bucket) error { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - return s.kv.Update(ctx, func(tx Tx) error { - return s.createBucket(ctx, tx, b) - }) -} - -// CreateBucketTx is used when importing kv as a library -func (s *Service) CreateBucketTx(ctx context.Context, tx Tx, b *influxdb.Bucket) (err error) { - return s.createBucket(ctx, tx, b) -} - -func (s *Service) createBucket(ctx context.Context, tx Tx, b *influxdb.Bucket) (err error) { - if b.OrgID.Valid() { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - _, pe := s.findOrganizationByID(ctx, tx, b.OrgID) - if pe != nil { - return &influxdb.Error{ - Err: pe, - } - } - } - - if err := s.uniqueBucketName(ctx, tx, b); err != nil { - return err - } - - if err := validBucketName(b.Name, b.Type); err != nil { - return err - } - - if b.ID, err = s.generateBucketID(ctx, tx); err != nil { - return err - } - - b.CreatedAt = s.Now() - b.UpdatedAt = s.Now() - - if err := s.appendBucketEventToLog(ctx, tx, b.ID, bucketCreatedEvent); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - v, err := json.Marshal(b) - if err != nil { - return influxdb.ErrInternalBucketServiceError(influxdb.OpCreateBucket, err) - } - if err := s.putBucket(ctx, tx, b, v); err != nil { - return err - } - - if err := s.createUserResourceMappingForOrg(ctx, tx, b.OrgID, b.ID, influxdb.BucketsResourceType); err != nil { - return err - } - - uid, _ := icontext.GetUserID(ctx) - return s.audit.Log(resource.Change{ - Type: resource.Create, - ResourceID: b.ID, - ResourceType: influxdb.BucketsResourceType, - OrganizationID: b.OrgID, - UserID: uid, - ResourceBody: v, - Time: time.Now(), - }) -} - -func (s *Service) generateBucketID(ctx context.Context, tx Tx) (influxdb.ID, error) { - return s.generateSafeID(ctx, tx, bucketBucket, s.BucketIDs) -} - -// PutBucket will put a bucket without setting an ID. -func (s *Service) PutBucket(ctx context.Context, b *influxdb.Bucket) error { - return s.kv.Update(ctx, func(tx Tx) error { - v, err := json.Marshal(b) - if err != nil { - return influxdb.ErrInternalBucketServiceError(influxdb.OpPutBucket, err) - } - - if err := s.putBucket(ctx, tx, b, v); err != nil { - return err - } - - uid, _ := icontext.GetUserID(ctx) - return s.audit.Log(resource.Change{ - Type: resource.Put, - ResourceID: b.ID, - ResourceType: influxdb.BucketsResourceType, - OrganizationID: b.OrgID, - UserID: uid, - ResourceBody: v, - Time: time.Now(), - }) - }) -} - -func (s *Service) putBucket(ctx context.Context, tx Tx, b *influxdb.Bucket, v []byte) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - encodedID, err := b.ID.Encode() - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - key, err := bucketIndexKey(b) - if err != nil { - return err - } - - idx, err := s.bucketsIndexBucket(tx) - if err != nil { - return err - } - - if err := idx.Put(key, encodedID); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - bkt, err := s.bucketsBucket(tx) - if bkt.Put(encodedID, v); err != nil { - return &influxdb.Error{ - Err: err, - } - } - return nil -} - -// bucketIndexKey is a combination of the orgID and the bucket name. -func bucketIndexKey(b *influxdb.Bucket) ([]byte, error) { - orgID, err := b.OrgID.Encode() - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - k := make([]byte, influxdb.IDLength+len(b.Name)) - copy(k, orgID) - copy(k[influxdb.IDLength:], []byte(b.Name)) - return k, nil -} - -// forEachBucket will iterate through all buckets while fn returns true. -func (s *Service) forEachBucket(ctx context.Context, tx Tx, descending bool, fn func(*influxdb.Bucket) bool) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - bkt, err := s.bucketsBucket(tx) - if err != nil { - return err - } - - direction := CursorAscending - if descending { - direction = CursorDescending - } - - cur, err := bkt.ForwardCursor(nil, WithCursorDirection(direction)) - if err != nil { - return err - } - - for k, v := cur.Next(); k != nil; k, v = cur.Next() { - b := &influxdb.Bucket{} - if err := json.Unmarshal(v, b); err != nil { - return err - } - - if !fn(b) { - break - } - } - - return nil -} - -func (s *Service) uniqueBucketName(ctx context.Context, tx Tx, b *influxdb.Bucket) error { - key, err := bucketIndexKey(b) - if err != nil { - return err - } - - // if the bucket name is not unique for this organization, then, do not - // allow creation. - err = s.unique(ctx, tx, bucketIndex, key) - if err == NotUniqueError { - return BucketAlreadyExistsError(b) - } - - return err -} - -// validBucketName reports any errors with bucket names -func validBucketName(name string, typ influxdb.BucketType) error { - // names starting with an underscore are reserved for system buckets - if strings.HasPrefix(name, "_") && typ != influxdb.BucketTypeSystem { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: fmt.Sprintf("bucket name %s is invalid. Buckets may not start with underscore", name), - Op: influxdb.OpCreateBucket, - } - } - // quotation marks will cause queries to fail - if strings.Contains(name, "\"") { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: fmt.Sprintf("bucket name %s is invalid. Bucket names may not include quotation marks", name), - Op: influxdb.OpCreateBucket, - } - } - return nil -} - -// UpdateBucket updates a bucket according the parameters set on upd. -func (s *Service) UpdateBucket(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var b *influxdb.Bucket - err := s.kv.Update(ctx, func(tx Tx) error { - bkt, err := s.updateBucket(ctx, tx, id, upd) - if err != nil { - return err - } - b = bkt - return nil - }) - - return b, err -} - -func (s *Service) updateBucket(ctx context.Context, tx Tx, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - b, err := s.findBucketByID(ctx, tx, id) - if err != nil { - return nil, err - } - - if upd.Name != nil && b.Type == influxdb.BucketTypeSystem { - err = &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "system buckets cannot be renamed", - } - - return nil, err - } - - if upd.RetentionPeriod != nil { - b.RetentionPeriod = *upd.RetentionPeriod - } - - if upd.Description != nil { - b.Description = *upd.Description - } - - if upd.Name != nil { - b0, err := s.findBucketByName(ctx, tx, b.OrgID, *upd.Name) - if err == nil && b0.ID != id { - return nil, &influxdb.Error{ - Code: influxdb.EConflict, - Msg: "bucket name is not unique", - } - } - - if err := validBucketName(*upd.Name, b.Type); err != nil { - return nil, err - } - - key, err := bucketIndexKey(b) - if err != nil { - return nil, err - } - idx, err := s.bucketsIndexBucket(tx) - if err != nil { - return nil, err - } - // Buckets are indexed by name and so the bucket index must be pruned when name is modified. - if err := idx.Delete(key); err != nil { - return nil, err - } - b.Name = *upd.Name - } - - b.UpdatedAt = s.Now() - - if err := s.appendBucketEventToLog(ctx, tx, b.ID, bucketUpdatedEvent); err != nil { - return nil, err - } - - v, err := json.Marshal(b) - if err != nil { - return nil, influxdb.ErrInternalBucketServiceError(influxdb.OpUpdateBucket, err) - } - - if err := s.putBucket(ctx, tx, b, v); err != nil { - return nil, err - } - - uid, _ := icontext.GetUserID(ctx) - if err := s.audit.Log(resource.Change{ - Type: resource.Update, - ResourceID: b.ID, - ResourceType: influxdb.BucketsResourceType, - OrganizationID: b.OrgID, - UserID: uid, - ResourceBody: v, - Time: time.Now(), - }); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return b, nil -} - -// DeleteBucket deletes a bucket and prunes it from the index. -func (s *Service) DeleteBucket(ctx context.Context, id influxdb.ID) error { - return s.kv.Update(ctx, func(tx Tx) error { - bucket, err := s.findBucketByID(ctx, tx, id) - if err != nil && !IsNotFound(err) { - return err - } - - if !IsNotFound(err) && bucket.Type == influxdb.BucketTypeSystem { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "system buckets cannot be deleted", - } - } - - if err := s.deleteBucket(ctx, tx, id); err != nil { - return err - } - - uid, _ := icontext.GetUserID(ctx) - return s.audit.Log(resource.Change{ - Type: resource.Delete, - ResourceID: id, - ResourceType: influxdb.BucketsResourceType, - OrganizationID: bucket.OrgID, - UserID: uid, - Time: time.Now(), - }) - }) -} - -func (s *Service) deleteBucket(ctx context.Context, tx Tx, id influxdb.ID) error { - b, pe := s.findBucketByID(ctx, tx, id) - if pe != nil { - return pe - } - - key, pe := bucketIndexKey(b) - if pe != nil { - return pe - } - - idx, err := s.bucketsIndexBucket(tx) - if err != nil { - return err - } - - if err := idx.Delete(key); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - encodedID, err := id.Encode() - if err != nil { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - bkt, err := s.bucketsBucket(tx) - if err != nil { - return err - } - - if err := bkt.Delete(encodedID); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - if err := s.deleteUserResourceMappings(ctx, tx, influxdb.UserResourceMappingFilter{ - ResourceID: id, - ResourceType: influxdb.BucketsResourceType, - }); err != nil { - return err - } - - return nil -} - -const bucketOperationLogKeyPrefix = "bucket" - -func encodeBucketOperationLogKey(id influxdb.ID) ([]byte, error) { - buf, err := id.Encode() - if err != nil { - return nil, err - } - return append([]byte(bucketOperationLogKeyPrefix), buf...), nil -} - -// GetBucketOperationLog retrieves a buckets operation log. -func (s *Service) GetBucketOperationLog(ctx context.Context, id influxdb.ID, opts influxdb.FindOptions) ([]*influxdb.OperationLogEntry, int, error) { - // TODO(desa): might be worthwhile to allocate a slice of size opts.Limit - log := []*influxdb.OperationLogEntry{} - - err := s.kv.View(ctx, func(tx Tx) error { - key, err := encodeBucketOperationLogKey(id) - if err != nil { - return err - } - - return s.ForEachLogEntryTx(ctx, tx, key, opts, func(v []byte, t time.Time) error { - e := &influxdb.OperationLogEntry{} - if err := json.Unmarshal(v, e); err != nil { - return err - } - e.Time = t - - log = append(log, e) - - return nil - }) - }) - - if err != nil && err != ErrKeyValueLogBoundsNotFound { - return nil, 0, err - } - - return log, len(log), nil -} - -// TODO(desa): what do we want these to be? -const ( - bucketCreatedEvent = "Bucket Created" - bucketUpdatedEvent = "Bucket Updated" -) - -func (s *Service) appendBucketEventToLog(ctx context.Context, tx Tx, id influxdb.ID, st string) error { - e := &influxdb.OperationLogEntry{ - Description: st, - } - // TODO(desa): this is fragile and non explicit since it requires an authorizer to be on context. It should be - // replaced with a higher level transaction so that adding to the log can take place in the http handler - // where the userID will exist explicitly. - a, err := icontext.GetAuthorizer(ctx) - if err == nil { - // Add the user to the log if you can, but don't error if its not there. - e.UserID = a.GetUserID() - } - - v, err := json.Marshal(e) - if err != nil { - return err - } - - k, err := encodeBucketOperationLogKey(id) - if err != nil { - return err - } - - return s.AddLogEntryTx(ctx, tx, k, v, s.Now()) -} - -// UnexpectedBucketError is used when the error comes from an internal system. -func UnexpectedBucketError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("unexpected error retrieving bucket's bucket; Err %v", err), - Op: "kv/bucketBucket", - } -} - -// UnexpectedBucketIndexError is used when the error comes from an internal system. -func UnexpectedBucketIndexError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("unexpected error retrieving bucket index; Err: %v", err), - Op: "kv/bucketIndex", - } -} - -// BucketAlreadyExistsError is used when creating a bucket with a name -// that already exists within an organization. -func BucketAlreadyExistsError(b *influxdb.Bucket) error { - return &influxdb.Error{ - Code: influxdb.EConflict, - Op: "kv/bucket", - Msg: fmt.Sprintf("bucket with name %s already exists", b.Name), - } -} diff --git a/kv/bucket_test.go b/kv/bucket_test.go deleted file mode 100644 index b06bc40f72..0000000000 --- a/kv/bucket_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltBucketService(t *testing.T) { - influxdbtesting.BucketService(initBoltBucketService, t) -} - -func initBoltBucketService(f influxdbtesting.BucketFields, t *testing.T) (influxdb.BucketService, string, func()) { - s, closeBolt, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new kv store: %v", err) - } - - svc, op, closeSvc := initBucketService(s, f, t) - return svc, op, func() { - closeSvc() - closeBolt() - } -} - -func initBucketService(s kv.SchemaStore, f influxdbtesting.BucketFields, t *testing.T) (influxdb.BucketService, string, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.OrgIDs = f.OrgIDs - svc.BucketIDs = f.BucketIDs - svc.IDGenerator = f.IDGenerator - svc.TimeGenerator = f.TimeGenerator - if f.TimeGenerator == nil { - svc.TimeGenerator = influxdb.RealTimeGenerator{} - } - - for _, o := range f.Organizations { - // new tenant services do this properly - o.ID = svc.OrgIDs.ID() - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate organizations: %s", err) - } - } - - for _, b := range f.Buckets { - // new tenant services do this properly - b.ID = svc.BucketIDs.ID() - if err := svc.PutBucket(ctx, b); err != nil { - t.Fatalf("failed to populate buckets: %s", err) - } - } - - return svc, kv.OpPrefix, func() { - for _, o := range f.Organizations { - if err := svc.DeleteOrganization(ctx, o.ID); err != nil { - t.Logf("failed to remove organization: %v", err) - } - } - for _, b := range f.Buckets { - if err := svc.DeleteBucket(ctx, b.ID); err != nil { - t.Logf("failed to remove bucket: %v", err) - } - } - } -} diff --git a/kv/document.go b/kv/document.go deleted file mode 100644 index a67804e1cf..0000000000 --- a/kv/document.go +++ /dev/null @@ -1,586 +0,0 @@ -package kv - -import ( - "context" - "encoding/json" - "path" - - "github.com/influxdata/influxdb/v2" -) - -const ( - documentContentBucket = "/documents/content" - documentMetaBucket = "/documents/meta" -) - -// DocumentStore implements influxdb.DocumentStore. -type DocumentStore struct { - service *Service - namespace string -} - -// CreateDocumentStore creates an instance of a document store by instantiating the buckets for the store. -func (s *Service) CreateDocumentStore(ctx context.Context, ns string) (influxdb.DocumentStore, error) { - // TODO(desa): keep track of which namespaces exist. - return s.createDocumentStore(ctx, ns) -} - -func (s *Service) createDocumentStore(ctx context.Context, ns string) (influxdb.DocumentStore, error) { - return &DocumentStore{ - namespace: ns, - service: s, - }, nil -} - -// FindDocumentStore finds the buckets associated with the namespace provided. -func (s *Service) FindDocumentStore(ctx context.Context, ns string) (influxdb.DocumentStore, error) { - var ds influxdb.DocumentStore - - err := s.kv.View(ctx, func(tx Tx) error { - if _, err := tx.Bucket([]byte(path.Join(ns, documentContentBucket))); err != nil { - return err - } - - if _, err := tx.Bucket([]byte(path.Join(ns, documentMetaBucket))); err != nil { - return err - } - - ds = &DocumentStore{ - namespace: ns, - service: s, - } - - return nil - }) - - if err != nil { - return nil, err - } - - return ds, nil -} - -// CreateDocument creates an instance of a document and sets the ID. After which it applies each of the options provided. -func (s *DocumentStore) CreateDocument(ctx context.Context, d *influxdb.Document) error { - return s.service.kv.Update(ctx, func(tx Tx) error { - // Check that labels exist before creating the document. - // Mapping creation would check for that, but cannot anticipate that until we - // have a valid document ID. - for _, l := range d.Labels { - if _, err := s.service.findLabelByID(ctx, tx, l.ID); err != nil { - return err - } - } - - err := s.service.createDocument(ctx, tx, s.namespace, d) - if err != nil { - return err - } - - for orgID := range d.Organizations { - if err := s.service.addDocumentOwner(ctx, tx, orgID, d.ID); err != nil { - return err - } - } - - for _, l := range d.Labels { - if err := s.addDocumentLabelMapping(ctx, tx, d.ID, l.ID); err != nil { - return err - } - } - return nil - }) -} - -func (s *Service) createDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error { - d.ID = s.IDGenerator.ID() - d.Meta.CreatedAt = s.Now() - d.Meta.UpdatedAt = s.Now() - return s.putDocument(ctx, tx, ns, d) -} - -// Affo: the only resource in which a org owns something and not a precise user. -func (s *Service) addDocumentOwner(ctx context.Context, tx Tx, orgID influxdb.ID, docID influxdb.ID) error { - // In this case UserID refers to an organization rather than a user. - m := &influxdb.UserResourceMapping{ - UserID: orgID, - UserType: influxdb.Owner, - MappingType: influxdb.OrgMappingType, - ResourceType: influxdb.DocumentsResourceType, - ResourceID: docID, - } - return s.createUserResourceMapping(ctx, tx, m) -} - -func (s *DocumentStore) addDocumentLabelMapping(ctx context.Context, tx Tx, docID, labelID influxdb.ID) error { - m := &influxdb.LabelMapping{ - LabelID: labelID, - ResourceType: influxdb.DocumentsResourceType, - ResourceID: docID, - } - if err := s.service.createLabelMapping(ctx, tx, m); err != nil { - return err - } - return nil -} - -func (s *Service) putDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error { - if err := s.putDocumentMeta(ctx, tx, ns, d.ID, d.Meta); err != nil { - return err - } - - if err := s.putDocumentContent(ctx, tx, ns, d.ID, d.Content); err != nil { - return err - } - - // TODO(desa): index document meta - - return nil -} - -func (s *Service) putAtID(ctx context.Context, tx Tx, bucket string, id influxdb.ID, i interface{}) error { - v, err := json.Marshal(i) - if err != nil { - return err - } - - k, err := id.Encode() - if err != nil { - return err - } - - b, err := tx.Bucket([]byte(bucket)) - if err != nil { - return err - } - - if err := b.Put(k, v); err != nil { - return err - } - - return nil -} - -func (s *Service) putDocumentContent(ctx context.Context, tx Tx, ns string, id influxdb.ID, data interface{}) error { - return s.putAtID(ctx, tx, path.Join(ns, documentContentBucket), id, data) -} - -func (s *Service) putDocumentMeta(ctx context.Context, tx Tx, ns string, id influxdb.ID, m influxdb.DocumentMeta) error { - return s.putAtID(ctx, tx, path.Join(ns, documentMetaBucket), id, m) -} - -func (s *DocumentStore) PutDocument(ctx context.Context, d *influxdb.Document) error { - return s.service.kv.Update(ctx, func(tx Tx) error { - return s.service.putDocument(ctx, tx, s.namespace, d) - }) -} - -func (s *Service) findDocumentsByID(ctx context.Context, tx Tx, ns string, ids ...influxdb.ID) ([]*influxdb.Document, error) { - ds := make([]*influxdb.Document, 0, len(ids)) - - for _, id := range ids { - d, err := s.findDocumentByID(ctx, tx, ns, id) - if err != nil { - return nil, err - } - - ds = append(ds, d) - } - - return ds, nil -} - -func (s *Service) findDocumentByID(ctx context.Context, tx Tx, ns string, id influxdb.ID) (*influxdb.Document, error) { - m, err := s.findDocumentMetaByID(ctx, tx, ns, id) - if err != nil { - return nil, err - } - - return &influxdb.Document{ - ID: id, - Meta: *m, - Labels: []*influxdb.Label{}, - }, nil -} - -func (s *Service) findByID(ctx context.Context, tx Tx, bucket string, id influxdb.ID, i interface{}) error { - b, err := tx.Bucket([]byte(bucket)) - if err != nil { - return err - } - - k, err := id.Encode() - if err != nil { - return err - } - - v, err := b.Get(k) - if err != nil { - return err - } - - if err := json.Unmarshal(v, i); err != nil { - return err - } - - return nil -} - -func (s *Service) findDocumentMetaByID(ctx context.Context, tx Tx, ns string, id influxdb.ID) (*influxdb.DocumentMeta, error) { - m := &influxdb.DocumentMeta{} - - if err := s.findByID(ctx, tx, path.Join(ns, documentMetaBucket), id, m); err != nil { - return nil, err - } - - return m, nil -} - -func (s *Service) findDocumentContentByID(ctx context.Context, tx Tx, ns string, id influxdb.ID) (interface{}, error) { - var data interface{} - if err := s.findByID(ctx, tx, path.Join(ns, documentContentBucket), id, &data); err != nil { - return nil, err - } - - return data, nil -} - -// FindDocument retrieves the specified document with all its content and labels. -func (s *DocumentStore) FindDocument(ctx context.Context, id influxdb.ID) (*influxdb.Document, error) { - var d *influxdb.Document - err := s.service.kv.View(ctx, func(tx Tx) error { - m, err := s.service.findDocumentMetaByID(ctx, tx, s.namespace, id) - if err != nil { - return err - } - c, err := s.service.findDocumentContentByID(ctx, tx, s.namespace, id) - if err != nil { - return err - } - d = &influxdb.Document{ - ID: id, - Meta: *m, - Content: c, - } - if err := s.decorateDocumentWithLabels(ctx, tx, d); err != nil { - return err - } - if err := s.decorateDocumentWithOrgs(ctx, tx, d); err != nil { - return err - } - - return nil - }) - - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: influxdb.ErrDocumentNotFound, - } - } - - if err != nil { - return nil, err - } - - return d, nil -} - -// FindDocuments retrieves all documents returned by the document find options. -func (s *DocumentStore) FindDocuments(ctx context.Context, opts ...influxdb.DocumentFindOptions) ([]*influxdb.Document, error) { - var ds []*influxdb.Document - err := s.service.kv.View(ctx, func(tx Tx) error { - if len(opts) == 0 { - // TODO(desa): might be a better way to do get all. - if err := s.service.findDocuments(ctx, tx, s.namespace, &ds); err != nil { - return err - } - - return nil - } - - idx := &DocumentIndex{ - service: s.service, - namespace: s.namespace, - tx: tx, - ctx: ctx, - } - - dd := &DocumentDecorator{} - - var ids []influxdb.ID - for _, opt := range opts { - is, err := opt(idx, dd) - if err != nil { - return err - } - - ids = append(ids, is...) - } - - docs, err := s.service.findDocumentsByID(ctx, tx, s.namespace, ids...) - if err != nil { - return err - } - - if dd.data { - for _, doc := range docs { - d, err := s.service.findDocumentContentByID(ctx, tx, s.namespace, doc.ID) - if err != nil { - return err - } - doc.Content = d - } - } - - if dd.labels { - for _, doc := range docs { - if err := s.decorateDocumentWithLabels(ctx, tx, doc); err != nil { - return err - } - } - } - - if dd.orgs { - for _, doc := range docs { - if err := s.decorateDocumentWithOrgs(ctx, tx, doc); err != nil { - return err - } - } - } - - ds = append(ds, docs...) - - return nil - }) - - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: influxdb.ErrDocumentNotFound, - } - } - - if err != nil { - return nil, err - } - - return ds, nil -} - -func (s *Service) findDocuments(ctx context.Context, tx Tx, ns string, ds *[]*influxdb.Document) error { - metab, err := tx.Bucket([]byte(path.Join(ns, documentMetaBucket))) - if err != nil { - return err - } - - cur, err := metab.ForwardCursor(nil) - if err != nil { - return err - } - - for k, v := cur.Next(); len(k) != 0; k, v = cur.Next() { - d := &influxdb.Document{} - if err := d.ID.Decode(k); err != nil { - return err - } - - if err := json.Unmarshal(v, &d.Meta); err != nil { - return err - } - - *ds = append(*ds, d) - } - - return nil -} - -func (s *Service) getDocumentsAccessors(ctx context.Context, tx Tx, docID influxdb.ID) (map[influxdb.ID]influxdb.UserType, error) { - f := influxdb.UserResourceMappingFilter{ - ResourceType: influxdb.DocumentsResourceType, - ResourceID: docID, - } - ms, err := s.findUserResourceMappings(ctx, tx, f) - if err != nil { - return nil, err - } - // The only URM created when creating a document is - // from an org to the document (not a user). - orgs := make(map[influxdb.ID]influxdb.UserType, len(ms)) - for _, m := range ms { - if m.MappingType == influxdb.OrgMappingType { - orgs[m.UserID] = m.UserType - } - } - return orgs, nil -} - -// DeleteDocument removes the specified document. -func (s *DocumentStore) DeleteDocument(ctx context.Context, id influxdb.ID) error { - return s.service.kv.Update(ctx, func(tx Tx) error { - if err := s.service.deleteDocument(ctx, tx, s.namespace, id); err != nil { - if IsNotFound(err) { - return &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: influxdb.ErrDocumentNotFound, - } - } - return err - } - return nil - }) -} - -// DeleteDocuments removes all documents returned by the options. -func (s *DocumentStore) DeleteDocuments(ctx context.Context, opts ...influxdb.DocumentFindOptions) error { - return s.service.kv.Update(ctx, func(tx Tx) error { - idx := &DocumentIndex{ - service: s.service, - namespace: s.namespace, - tx: tx, - ctx: ctx, - writable: true, - } - dd := &DocumentDecorator{writable: true} - - var ids []influxdb.ID - for _, opt := range opts { - dids, err := opt(idx, dd) - if err != nil { - return err - } - - ids = append(ids, dids...) - } - - for _, id := range ids { - if err := s.service.deleteDocument(ctx, tx, s.namespace, id); err != nil { - if IsNotFound(err) { - return &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: influxdb.ErrDocumentNotFound, - } - } - return err - } - } - return nil - }) -} - -func (s *Service) removeDocumentAccess(ctx context.Context, tx Tx, orgID, docID influxdb.ID) error { - filter := influxdb.UserResourceMappingFilter{ - ResourceID: docID, - UserID: orgID, - } - if err := s.deleteUserResourceMapping(ctx, tx, filter); err != nil { - return err - } - return nil -} - -func (s *Service) deleteDocument(ctx context.Context, tx Tx, ns string, id influxdb.ID) error { - // Delete mappings. - orgs, err := s.getDocumentsAccessors(ctx, tx, id) - if err != nil { - return err - } - for orgID := range orgs { - if err := s.removeDocumentAccess(ctx, tx, orgID, id); err != nil { - return err - } - } - - // Delete document. - if _, err := s.findDocumentMetaByID(ctx, tx, ns, id); err != nil { - return err - } - if err := s.deleteDocumentMeta(ctx, tx, ns, id); err != nil { - return err - } - if err := s.deleteDocumentContent(ctx, tx, ns, id); err != nil { - return err - } - - // TODO(desa): deindex document meta - - return nil -} - -func (s *Service) deleteAtID(ctx context.Context, tx Tx, bucket string, id influxdb.ID) error { - k, err := id.Encode() - if err != nil { - return err - } - - b, err := tx.Bucket([]byte(bucket)) - if err != nil { - return err - } - - if err := b.Delete(k); err != nil { - return err - } - - return nil -} - -func (s *Service) deleteDocumentContent(ctx context.Context, tx Tx, ns string, id influxdb.ID) error { - return s.deleteAtID(ctx, tx, path.Join(ns, documentContentBucket), id) -} - -func (s *Service) deleteDocumentMeta(ctx context.Context, tx Tx, ns string, id influxdb.ID) error { - return s.deleteAtID(ctx, tx, path.Join(ns, documentMetaBucket), id) -} - -// UpdateDocument updates the document. -func (s *DocumentStore) UpdateDocument(ctx context.Context, d *influxdb.Document) error { - return s.service.kv.Update(ctx, func(tx Tx) error { - if err := s.service.updateDocument(ctx, tx, s.namespace, d); err != nil { - return err - } - - if err := s.decorateDocumentWithLabels(ctx, tx, d); err != nil { - return err - } - - return nil - }) -} - -func (s *Service) updateDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error { - // TODO(desa): deindex meta - d.Meta.UpdatedAt = s.Now() - if err := s.putDocument(ctx, tx, ns, d); err != nil { - return err - } - - return nil -} - -func (s *DocumentStore) decorateDocumentWithLabels(ctx context.Context, tx Tx, d *influxdb.Document) error { - var ls []*influxdb.Label - f := influxdb.LabelMappingFilter{ - ResourceID: d.ID, - ResourceType: influxdb.DocumentsResourceType, - } - if err := s.service.findResourceLabels(ctx, tx, f, &ls); err != nil { - return err - } - - d.Labels = append(d.Labels, ls...) - return nil -} - -func (s *DocumentStore) decorateDocumentWithOrgs(ctx context.Context, tx Tx, d *influxdb.Document) error { - // If the orgs are already there, then this is a nop. - if len(d.Organizations) > 0 { - return nil - } - orgs, err := s.service.getDocumentsAccessors(ctx, tx, d.ID) - if err != nil { - return err - } - d.Organizations = orgs - return nil -} diff --git a/kv/document_options.go b/kv/document_options.go deleted file mode 100644 index cadf369ef5..0000000000 --- a/kv/document_options.go +++ /dev/null @@ -1,216 +0,0 @@ -package kv - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2" -) - -// DocumentIndex implements influxdb.DocumentIndex. It is used to access labels/owners of documents. -type DocumentIndex struct { - service *Service - namespace string - - ctx context.Context - tx Tx - writable bool -} - -// FindLabelByID retrieves a label by id. -func (i *DocumentIndex) FindLabelByID(id influxdb.ID) error { - _, err := i.service.findLabelByID(i.ctx, i.tx, id) - return err -} - -// UsersOrgs retrieves a list of all orgs that a user is an accessor of. -func (i *DocumentIndex) UsersOrgs(userID influxdb.ID) ([]influxdb.ID, error) { - f := influxdb.UserResourceMappingFilter{ - UserID: userID, - ResourceType: influxdb.OrgsResourceType, - } - - ms, err := i.service.findUserResourceMappings(i.ctx, i.tx, f) - if err != nil { - return nil, err - } - - ids := make([]influxdb.ID, 0, len(ms)) - for _, m := range ms { - ids = append(ids, m.ResourceID) - } - - return ids, nil -} - -// IsOrgAccessor checks to see if the user is an accessor of the org provided. If the operation -// is writable it ensures that the user is owner. -func (i *DocumentIndex) IsOrgAccessor(userID influxdb.ID, orgID influxdb.ID) error { - f := influxdb.UserResourceMappingFilter{ - UserID: userID, - ResourceType: influxdb.OrgsResourceType, - ResourceID: orgID, - } - - if i.writable { - f.UserType = influxdb.Owner - } - - ms, err := i.service.findUserResourceMappings(i.ctx, i.tx, f) - if err != nil { - return err - } - - for _, m := range ms { - switch m.UserType { - case influxdb.Owner, influxdb.Member: - return nil - default: - continue - } - } - - return &influxdb.Error{ - Code: influxdb.EUnauthorized, - Msg: "user is not org member", - } -} - -func (i *DocumentIndex) ownerExists(ownerType string, ownerID influxdb.ID) error { - switch ownerType { - case "org": - if _, err := i.service.findOrganizationByID(i.ctx, i.tx, ownerID); err != nil { - return err - } - case "user": - if _, err := i.service.findUserByID(i.ctx, i.tx, ownerID); err != nil { - return err - } - default: - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("unknown owner type %q", ownerType), - } - } - - return nil -} - -// FindOrganizationByName retrieves the organization ID of the org provided. -func (i *DocumentIndex) FindOrganizationByName(org string) (influxdb.ID, error) { - o, err := i.service.findOrganizationByName(i.ctx, i.tx, org) - if err != nil { - return influxdb.InvalidID(), err - } - return o.ID, nil -} - -// FindOrganizationByID checks if the org existence by the org id provided. -func (i *DocumentIndex) FindOrganizationByID(id influxdb.ID) error { - _, err := i.service.findOrganizationByID(i.ctx, i.tx, id) - if err != nil { - return err - } - return nil -} - -// GetDocumentsAccessors retrieves the list of accessors of a document. -func (i *DocumentIndex) GetDocumentsAccessors(docID influxdb.ID) ([]influxdb.ID, error) { - f := influxdb.UserResourceMappingFilter{ - ResourceType: influxdb.DocumentsResourceType, - ResourceID: docID, - } - if i.writable { - f.UserType = influxdb.Owner - } - ms, err := i.service.findUserResourceMappings(i.ctx, i.tx, f) - if err != nil { - return nil, err - } - - ids := make([]influxdb.ID, 0, len(ms)) - for _, m := range ms { - if m.MappingType == influxdb.UserMappingType { - continue - } - // TODO(desa): this is really an orgID, eventually we should support users and org as owners of documents - ids = append(ids, m.UserID) - } - - return ids, nil -} - -// GetAccessorsDocuments retrieves the list of documents a user is allowed to access. -func (i *DocumentIndex) GetAccessorsDocuments(ownerType string, ownerID influxdb.ID) ([]influxdb.ID, error) { - if err := i.ownerExists(ownerType, ownerID); err != nil { - return nil, err - } - - f := influxdb.UserResourceMappingFilter{ - UserID: ownerID, - ResourceType: influxdb.DocumentsResourceType, - } - if i.writable { - f.UserType = influxdb.Owner - } - ms, err := i.service.findUserResourceMappings(i.ctx, i.tx, f) - if err != nil { - return nil, err - } - - ids := make([]influxdb.ID, 0, len(ms)) - for _, m := range ms { - ids = append(ids, m.ResourceID) - } - - return ids, nil -} - -// DocumentDecorator is used to communication the decoration of documents to the -// document store. -type DocumentDecorator struct { - data bool - labels bool - orgs bool - - writable bool -} - -// IncludeContent signals that the document should include its content when returned. -func (d *DocumentDecorator) IncludeContent() error { - if d.writable { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: "cannot include data in document", - } - } - - d.data = true - return nil -} - -// IncludeLabels signals that the document should include its labels when returned. -func (d *DocumentDecorator) IncludeLabels() error { - if d.writable { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: "cannot include labels in document", - } - } - - d.labels = true - return nil -} - -// IncludeOwner signals that the document should include its owner. -func (d *DocumentDecorator) IncludeOrganizations() error { - if d.writable { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: "cannot include labels in document", - } - } - - d.orgs = true - return nil -} diff --git a/kv/document_test.go b/kv/document_test.go deleted file mode 100644 index 162d484509..0000000000 --- a/kv/document_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package kv_test - -import ( - "testing" - - influxdbtesting "github.com/influxdata/influxdb/v2/testing" -) - -func TestBoltDocumentStore(t *testing.T) { - boltStore, closeBolt, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new bolt kv store: %v", err) - } - defer closeBolt() - - t.Run("bolt", influxdbtesting.NewDocumentIntegrationTest(boltStore)) -} diff --git a/kv/errors.go b/kv/errors.go new file mode 100644 index 0000000000..8a38935d26 --- /dev/null +++ b/kv/errors.go @@ -0,0 +1,23 @@ +package kv + +import ( + "fmt" + + "github.com/influxdata/influxdb/v2" +) + +// UnexpectedIndexError is used when the error comes from an internal system. +func UnexpectedIndexError(err error) *influxdb.Error { + return &influxdb.Error{ + Code: influxdb.EInternal, + Msg: fmt.Sprintf("unexpected error retrieving index; Err: %v", err), + Op: "kv/index", + } +} + +// NotUniqueError is used when attempting to create a resource that already +// exists. +var NotUniqueError = &influxdb.Error{ + Code: influxdb.EConflict, + Msg: "name already exists", +} diff --git a/kv/initial_migration.go b/kv/initial_migration.go index 6593d19aaa..9b7d45c53b 100644 --- a/kv/initial_migration.go +++ b/kv/initial_migration.go @@ -22,33 +22,33 @@ func (m InitialMigration) Up(ctx context.Context, store SchemaStore) error { // defined in NewInitialMigration. for _, bucket := range [][]byte{ - authBucket, - authIndex, - bucketBucket, - bucketIndex, + []byte("authorizationsv1"), + []byte("authorizationindexv1"), + []byte("bucketsv1"), + []byte("bucketindexv1"), []byte("dashboardsv2"), []byte("orgsdashboardsv1"), []byte("dashboardcellviewsv1"), kvlogBucket, kvlogIndex, - labelBucket, - labelMappingBucket, - labelIndex, - onboardingBucket, - organizationBucket, - organizationIndex, + []byte("labelsv1"), + []byte("labelmappingsv1"), + []byte("labelindexv1"), + []byte("onboardingv1"), + []byte("organizationsv1"), + []byte("organizationindexv1"), taskBucket, taskRunBucket, taskIndexBucket, - userpasswordBucket, + []byte("userspasswordv1"), scrapersBucket, - secretBucket, + []byte("secretsv1"), []byte("telegrafv1"), []byte("telegrafPluginsv1"), - urmBucket, + []byte("userresourcemappingsv1"), []byte("notificationRulev1"), - userBucket, - userIndex, + []byte("usersv1"), + []byte("userindexv1"), sourceBucket, // these are the "document" (aka templates) key prefixes []byte("templates/documents/content"), diff --git a/kv/kvlog_test.go b/kv/kvlog_test.go index 4b28c8c8b5..08f6ecd455 100644 --- a/kv/kvlog_test.go +++ b/kv/kvlog_test.go @@ -6,6 +6,7 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2/tenant" influxdbtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -29,7 +30,7 @@ func initBoltKeyValueLog(f influxdbtesting.KeyValueLogFields, t *testing.T) (inf func initKeyValueLog(s kv.SchemaStore, f influxdbtesting.KeyValueLogFields, t *testing.T) (influxdb.KeyValueLog, func()) { ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) + svc := kv.NewService(zaptest.NewLogger(t), s, tenant.NewService(tenant.NewStore(s))) for _, e := range f.LogEntries { if err := svc.AddLogEntry(ctx, e.Key, e.Value, e.Time); err != nil { diff --git a/kv/label.go b/kv/label.go deleted file mode 100644 index e077c5a06c..0000000000 --- a/kv/label.go +++ /dev/null @@ -1,659 +0,0 @@ -package kv - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kit/tracing" -) - -var ( - labelBucket = []byte("labelsv1") - labelMappingBucket = []byte("labelmappingsv1") - labelIndex = []byte("labelindexv1") -) - -// FindLabelByID finds a label by its ID -func (s *Service) FindLabelByID(ctx context.Context, id influxdb.ID) (*influxdb.Label, error) { - var l *influxdb.Label - - err := s.kv.View(ctx, func(tx Tx) error { - label, pe := s.findLabelByID(ctx, tx, id) - if pe != nil { - return pe - } - l = label - return nil - }) - - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return l, nil -} - -func (s *Service) findLabelByID(ctx context.Context, tx Tx, id influxdb.ID) (*influxdb.Label, error) { - encodedID, err := id.Encode() - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - b, err := tx.Bucket(labelBucket) - if err != nil { - return nil, err - } - - v, err := b.Get(encodedID) - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: influxdb.ErrLabelNotFound, - } - } - - if err != nil { - return nil, err - } - - var l influxdb.Label - if err := json.Unmarshal(v, &l); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return &l, nil -} - -func filterLabelsFn(filter influxdb.LabelFilter) func(l *influxdb.Label) bool { - return func(label *influxdb.Label) bool { - return (filter.Name == "" || (strings.EqualFold(filter.Name, label.Name))) && - ((filter.OrgID == nil) || (filter.OrgID != nil && *filter.OrgID == label.OrgID)) - } -} - -// FindLabels returns a list of labels that match a filter. -func (s *Service) FindLabels(ctx context.Context, filter influxdb.LabelFilter, opt ...influxdb.FindOptions) ([]*influxdb.Label, error) { - ls := []*influxdb.Label{} - err := s.kv.View(ctx, func(tx Tx) error { - labels, err := s.findLabels(ctx, tx, filter) - if err != nil { - return err - } - ls = labels - return nil - }) - - if err != nil { - return nil, err - } - - return ls, nil -} - -func (s *Service) findLabels(ctx context.Context, tx Tx, filter influxdb.LabelFilter) ([]*influxdb.Label, error) { - ls := []*influxdb.Label{} - filterFn := filterLabelsFn(filter) - err := s.forEachLabel(ctx, tx, func(l *influxdb.Label) bool { - if filterFn(l) { - ls = append(ls, l) - } - return true - }) - - if err != nil { - return nil, err - } - - return ls, nil -} - -func decodeLabelMappingKey(key []byte) (resourceID influxdb.ID, labelID influxdb.ID, err error) { - if len(key) != 2*influxdb.IDLength { - return 0, 0, &influxdb.Error{Code: influxdb.EInvalid, Msg: "malformed label mapping key (please report this error)"} - } - - if err := (&resourceID).Decode(key[:influxdb.IDLength]); err != nil { - return 0, 0, &influxdb.Error{Code: influxdb.EInvalid, Msg: "bad resource id", Err: influxdb.ErrInvalidID} - } - - if err := (&labelID).Decode(key[influxdb.IDLength:]); err != nil { - return 0, 0, &influxdb.Error{Code: influxdb.EInvalid, Msg: "bad label id", Err: influxdb.ErrInvalidID} - } - - return resourceID, labelID, nil -} - -func (s *Service) findResourceLabels(ctx context.Context, tx Tx, filter influxdb.LabelMappingFilter, ls *[]*influxdb.Label) error { - if !filter.ResourceID.Valid() { - return &influxdb.Error{Code: influxdb.EInvalid, Msg: "filter requires a valid resource id", Err: influxdb.ErrInvalidID} - } - idx, err := tx.Bucket(labelMappingBucket) - if err != nil { - return err - } - - prefix, err := filter.ResourceID.Encode() - if err != nil { - return err - } - - cur, err := idx.ForwardCursor(prefix, WithCursorPrefix(prefix)) - if err != nil { - return err - } - - for k, _ := cur.Next(); k != nil; k, _ = cur.Next() { - _, id, err := decodeLabelMappingKey(k) - if err != nil { - return err - } - - l, err := s.findLabelByID(ctx, tx, id) - if l == nil && err != nil { - // TODO(jm): return error instead of continuing once orphaned mappings are fixed - // (see https://github.com/influxdata/influxdb/issues/11278) - continue - } - - *ls = append(*ls, l) - } - return nil -} - -func (s *Service) FindResourceLabels(ctx context.Context, filter influxdb.LabelMappingFilter) ([]*influxdb.Label, error) { - ls := []*influxdb.Label{} - if err := s.kv.View(ctx, func(tx Tx) error { - return s.findResourceLabels(ctx, tx, filter, &ls) - }); err != nil { - return nil, err - } - - return ls, nil -} - -// CreateLabelMapping creates a new mapping between a resource and a label. -func (s *Service) CreateLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.createLabelMapping(ctx, tx, m) - }) -} - -// createLabelMapping creates a new mapping between a resource and a label. -func (s *Service) createLabelMapping(ctx context.Context, tx Tx, m *influxdb.LabelMapping) error { - if _, err := s.findLabelByID(ctx, tx, m.LabelID); err != nil { - return err - } - - ls := []*influxdb.Label{} - err := s.findResourceLabels(ctx, tx, influxdb.LabelMappingFilter{ResourceID: m.ResourceID, ResourceType: m.ResourceType}, &ls) - if err != nil { - return err - } - for i := 0; i < len(ls); i++ { - if ls[i].ID == m.LabelID { - return influxdb.ErrLabelExistsOnResource - } - } - - if err := s.putLabelMapping(ctx, tx, m); err != nil { - return err - } - - return nil -} - -// DeleteLabelMapping deletes a label mapping. -func (s *Service) DeleteLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { - err := s.kv.Update(ctx, func(tx Tx) error { - return s.deleteLabelMapping(ctx, tx, m) - }) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - return nil -} - -func (s *Service) deleteLabelMapping(ctx context.Context, tx Tx, m *influxdb.LabelMapping) error { - key, err := labelMappingKey(m) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - idx, err := tx.Bucket(labelMappingBucket) - if err != nil { - return err - } - - if err := idx.Delete(key); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - return nil -} - -// CreateLabel creates a new label. -func (s *Service) CreateLabel(ctx context.Context, l *influxdb.Label) error { - err := s.kv.Update(ctx, func(tx Tx) error { - if err := l.Validate(); err != nil { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - l.Name = strings.TrimSpace(l.Name) - - if err := s.uniqueLabelName(ctx, tx, l); err != nil { - return err - } - - l.ID = s.IDGenerator.ID() - - if err := s.putLabel(ctx, tx, l); err != nil { - return err - } - - if err := s.createUserResourceMappingForOrg(ctx, tx, l.OrgID, l.ID, influxdb.LabelsResourceType); err != nil { - return err - } - - return nil - }) - - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - return nil -} - -// PutLabel creates a label from the provided struct, without generating a new ID. -func (s *Service) PutLabel(ctx context.Context, l *influxdb.Label) error { - return s.kv.Update(ctx, func(tx Tx) error { - var err error - pe := s.putLabel(ctx, tx, l) - if pe != nil { - err = pe - } - return err - }) -} - -// CreateUserResourceMappingForOrg is a public function that calls createUserResourceMappingForOrg used only for the label service -// it can be removed when URMs are removed from the label service -func (s *Service) CreateUserResourceMappingForOrg(ctx context.Context, tx Tx, orgID influxdb.ID, resID influxdb.ID, resType influxdb.ResourceType) error { - err := s.createUserResourceMappingForOrg(ctx, tx, orgID, resID, resType) - - return err -} - -func (s *Service) createUserResourceMappingForOrg(ctx context.Context, tx Tx, orgID influxdb.ID, resID influxdb.ID, resType influxdb.ResourceType) error { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - ms, err := s.findUserResourceMappings(ctx, tx, influxdb.UserResourceMappingFilter{ - ResourceType: influxdb.OrgsResourceType, - ResourceID: orgID, - }) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - for _, m := range ms { - if err := s.createUserResourceMapping(ctx, tx, &influxdb.UserResourceMapping{ - ResourceType: resType, - ResourceID: resID, - UserID: m.UserID, - UserType: m.UserType, - }); err != nil { - return &influxdb.Error{ - Err: err, - } - } - } - - return nil -} - -func labelMappingKey(m *influxdb.LabelMapping) ([]byte, error) { - lid, err := m.LabelID.Encode() - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - rid, err := m.ResourceID.Encode() - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - key := make([]byte, influxdb.IDLength+influxdb.IDLength) // len(rid) + len(lid) - copy(key, rid) - copy(key[len(rid):], lid) - - return key, nil -} - -func (s *Service) forEachLabel(ctx context.Context, tx Tx, fn func(*influxdb.Label) bool) error { - b, err := tx.Bucket(labelBucket) - if err != nil { - return err - } - - cur, err := b.ForwardCursor(nil) - if err != nil { - return err - } - - for k, v := cur.Next(); k != nil; k, v = cur.Next() { - l := &influxdb.Label{} - if err := json.Unmarshal(v, l); err != nil { - return err - } - if !fn(l) { - break - } - } - - return nil -} - -// UpdateLabel updates a label. -func (s *Service) UpdateLabel(ctx context.Context, id influxdb.ID, upd influxdb.LabelUpdate) (*influxdb.Label, error) { - var label *influxdb.Label - err := s.kv.Update(ctx, func(tx Tx) error { - labelResponse, pe := s.updateLabel(ctx, tx, id, upd) - if pe != nil { - return &influxdb.Error{ - Err: pe, - } - } - label = labelResponse - return nil - }) - - return label, err -} - -func (s *Service) updateLabel(ctx context.Context, tx Tx, id influxdb.ID, upd influxdb.LabelUpdate) (*influxdb.Label, error) { - label, err := s.findLabelByID(ctx, tx, id) - if err != nil { - return nil, err - } - - if len(upd.Properties) > 0 && label.Properties == nil { - label.Properties = make(map[string]string) - } - - for k, v := range upd.Properties { - if v == "" { - delete(label.Properties, k) - } else { - label.Properties[k] = v - } - } - - if upd.Name != "" { - upd.Name = strings.TrimSpace(upd.Name) - - idx, err := tx.Bucket(labelIndex) - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - key, err := labelIndexKey(label) - if err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - if err := idx.Delete(key); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - label.Name = upd.Name - if err := s.uniqueLabelName(ctx, tx, label); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - } - - if err := label.Validate(); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - if err := s.putLabel(ctx, tx, label); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return label, nil -} - -// set a label and overwrite any existing label -func (s *Service) putLabel(ctx context.Context, tx Tx, l *influxdb.Label) error { - v, err := json.Marshal(l) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - encodedID, err := l.ID.Encode() - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - idx, err := tx.Bucket(labelIndex) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - key, err := labelIndexKey(l) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - if err := idx.Put([]byte(key), encodedID); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - b, err := tx.Bucket(labelBucket) - if err != nil { - return err - } - - if err := b.Put(encodedID, v); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - return nil -} - -// PutLabelMapping writes a label mapping to boltdb -func (s *Service) PutLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { - return s.kv.Update(ctx, func(tx Tx) error { - var err error - pe := s.putLabelMapping(ctx, tx, m) - if pe != nil { - err = pe - } - return err - }) -} - -func (s *Service) putLabelMapping(ctx context.Context, tx Tx, m *influxdb.LabelMapping) error { - v, err := json.Marshal(m) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - key, err := labelMappingKey(m) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - idx, err := tx.Bucket(labelMappingBucket) - if err != nil { - return err - } - - if err := idx.Put(key, v); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - return nil -} - -// DeleteLabel deletes a label. -func (s *Service) DeleteLabel(ctx context.Context, id influxdb.ID) error { - err := s.kv.Update(ctx, func(tx Tx) error { - return s.deleteLabel(ctx, tx, id) - }) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - return nil -} - -func (s *Service) deleteLabel(ctx context.Context, tx Tx, id influxdb.ID) error { - label, err := s.findLabelByID(ctx, tx, id) - if err != nil { - return err - } - encodedID, idErr := id.Encode() - if idErr != nil { - return &influxdb.Error{ - Err: idErr, - } - } - - b, err := tx.Bucket(labelBucket) - if err != nil { - return err - } - - if err := b.Delete(encodedID); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - idx, err := tx.Bucket(labelIndex) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - key, err := labelIndexKey(label) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - - if err := idx.Delete(key); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - if err := s.deleteUserResourceMappings(ctx, tx, influxdb.UserResourceMappingFilter{ - ResourceID: id, - ResourceType: influxdb.LabelsResourceType, - }); err != nil { - return err - } - - return nil -} - -// labelAlreadyExistsError is used when creating a new label with -// a name that has already been used. Label names must be unique. -func labelAlreadyExistsError(lbl *influxdb.Label) error { - return &influxdb.Error{ - Code: influxdb.EConflict, - Msg: fmt.Sprintf("label with name %s already exists", lbl.Name), - } -} - -func labelIndexKey(l *influxdb.Label) ([]byte, error) { - orgID, err := l.OrgID.Encode() - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - k := make([]byte, influxdb.IDLength+len(l.Name)) - copy(k, orgID) - copy(k[influxdb.IDLength:], []byte(strings.ToLower((l.Name)))) - return k, nil -} - -func (s *Service) uniqueLabelName(ctx context.Context, tx Tx, lbl *influxdb.Label) error { - key, err := labelIndexKey(lbl) - if err != nil { - return err - } - - // labels are unique by `organization:label_name` - err = s.unique(ctx, tx, labelIndex, key) - if err == NotUniqueError { - return labelAlreadyExistsError(lbl) - } - return err -} diff --git a/kv/label_test.go b/kv/label_test.go deleted file mode 100644 index acb8a8a3ca..0000000000 --- a/kv/label_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltLabelService(t *testing.T) { - influxdbtesting.LabelService(initBoltLabelService, t) -} - -func initBoltLabelService(f influxdbtesting.LabelFields, t *testing.T) (influxdb.LabelService, string, func()) { - s, closeBolt, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new kv store: %v", err) - } - - svc, op, closeSvc := initLabelService(s, f, t) - return svc, op, func() { - closeSvc() - closeBolt() - } -} - -func initLabelService(s kv.SchemaStore, f influxdbtesting.LabelFields, t *testing.T) (influxdb.LabelService, string, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.IDGenerator = f.IDGenerator - - for _, l := range f.Labels { - if err := svc.PutLabel(ctx, l); err != nil { - t.Fatalf("failed to populate labels: %v", err) - } - } - - for _, m := range f.Mappings { - if err := svc.PutLabelMapping(ctx, m); err != nil { - t.Fatalf("failed to populate label mappings: %v", err) - } - } - - return svc, kv.OpPrefix, func() { - for _, l := range f.Labels { - if err := svc.DeleteLabel(ctx, l.ID); err != nil { - t.Logf("failed to remove label: %v", err) - } - } - } -} diff --git a/kv/migration/all/0002_urm_by_user_index.go b/kv/migration/all/0002_urm_by_user_index.go index 8201c80d18..12c29cf9ee 100644 --- a/kv/migration/all/0002_urm_by_user_index.go +++ b/kv/migration/all/0002_urm_by_user_index.go @@ -1,6 +1,9 @@ package all -import "github.com/influxdata/influxdb/v2/kv" +import ( + "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2/tenant/index" +) // Migration0002_AddURMByUserIndex creates the URM by user index and populates missing entries based on the source. -var Migration0002_AddURMByUserIndex = kv.NewIndexMigration(kv.URMByUserIndexMapping, kv.WithIndexMigrationCleanup) +var Migration0002_AddURMByUserIndex = kv.NewIndexMigration(index.URMByUserIndexMapping, kv.WithIndexMigrationCleanup) diff --git a/kv/migration/all/0003_task_owners_test.go b/kv/migration/all/0003_task_owners_test.go index 7263f303b1..4d0ba1455b 100644 --- a/kv/migration/all/0003_task_owners_test.go +++ b/kv/migration/all/0003_task_owners_test.go @@ -7,9 +7,11 @@ import ( "github.com/benbjohnson/clock" "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" "github.com/influxdata/influxdb/v2/inmem" "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration" + "github.com/influxdata/influxdb/v2/tenant" "go.uber.org/zap/zaptest" ) @@ -114,18 +116,27 @@ func newService(t *testing.T, ctx context.Context) *testService { t.Fatal(err) } - ts.Service = kv.NewService(logger, ts.Store) + store := tenant.NewStore(ts.Store) + tenantSvc := tenant.NewService(store) + + authStore, err := authorization.NewStore(ts.Store) + if err != nil { + t.Fatal(err) + } + authSvc := authorization.NewService(authStore, tenantSvc) + + ts.Service = kv.NewService(logger, ts.Store, tenantSvc) ts.User = influxdb.User{Name: t.Name() + "-user"} - if err := ts.Service.CreateUser(ctx, &ts.User); err != nil { + if err := tenantSvc.CreateUser(ctx, &ts.User); err != nil { t.Fatal(err) } ts.Org = influxdb.Organization{Name: t.Name() + "-org"} - if err := ts.Service.CreateOrganization(ctx, &ts.Org); err != nil { + if err := tenantSvc.CreateOrganization(ctx, &ts.Org); err != nil { t.Fatal(err) } - if err := ts.Service.CreateUserResourceMapping(ctx, &influxdb.UserResourceMapping{ + if err := tenantSvc.CreateUserResourceMapping(ctx, &influxdb.UserResourceMapping{ ResourceType: influxdb.OrgsResourceType, ResourceID: ts.Org.ID, UserID: ts.User.ID, @@ -139,7 +150,7 @@ func newService(t *testing.T, ctx context.Context) *testService { UserID: ts.User.ID, Permissions: influxdb.OperPermissions(), } - if err := ts.Service.CreateAuthorization(context.Background(), &ts.Auth); err != nil { + if err := authSvc.CreateAuthorization(context.Background(), &ts.Auth); err != nil { t.Fatal(err) } diff --git a/kv/onboarding.go b/kv/onboarding.go deleted file mode 100644 index 80647cd5fb..0000000000 --- a/kv/onboarding.go +++ /dev/null @@ -1,177 +0,0 @@ -package kv - -import ( - "context" - "errors" - "fmt" - "github.com/influxdata/influxdb/v2" -) - -var ( - onboardingBucket = []byte("onboardingv1") - onboardingKey = []byte("onboarding_key") -) - -var _ influxdb.OnboardingService = (*Service)(nil) - -// IsOnboarding means if the initial setup of influxdb has happened. -// true means that the onboarding setup has not yet happened. -// false means that the onboarding has been completed. -func (s *Service) IsOnboarding(ctx context.Context) (bool, error) { - notSetup := true - err := s.kv.View(ctx, func(tx Tx) error { - bucket, err := tx.Bucket(onboardingBucket) - if err != nil { - return err - } - v, err := bucket.Get(onboardingKey) - // If the sentinel onboarding key is not found, then, setup - // has not been performed. - if IsNotFound(err) { - notSetup = true - return nil - } - if err != nil { - return err - } - // If the sentinel key has any bytes whatsoever, then, - if len(v) > 0 { - notSetup = false // this means that it is setup. I hate bools. - } - return nil - }) - return notSetup, err -} - -// PutOnboardingStatus will update the flag, -// so future onboarding request will be denied. -// true means that onboarding is NOT needed. -// false means that onboarding is needed. -func (s *Service) PutOnboardingStatus(ctx context.Context, hasBeenOnboarded bool) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.putOnboardingStatus(ctx, tx, hasBeenOnboarded) - }) -} - -func (s *Service) putOnboardingStatus(ctx context.Context, tx Tx, hasBeenOnboarded bool) error { - if hasBeenOnboarded { - return s.setOnboarded(ctx, tx) - } - return s.setOffboarded(ctx, tx) -} - -func (s *Service) setOffboarded(ctx context.Context, tx Tx) error { - bucket, err := tx.Bucket(onboardingBucket) - if err != nil { - // TODO(goller): check err - return err - } - err = bucket.Delete(onboardingKey) - if err != nil { - // TODO(goller): check err - return err - } - return nil -} - -func (s *Service) setOnboarded(ctx context.Context, tx Tx) error { - bucket, err := tx.Bucket(onboardingBucket) - if err != nil { - // TODO(goller): check err - return err - } - err = bucket.Put(onboardingKey, []byte{0x1}) - if err != nil { - // TODO(goller): check err - return err - } - return nil -} - -// OnboardInitialUser OnboardingResults from onboarding request, -// update db so this request will be disabled for the second run. -func (s *Service) OnboardInitialUser(ctx context.Context, req *influxdb.OnboardingRequest) (*influxdb.OnboardingResults, error) { - isOnboarding, err := s.IsOnboarding(ctx) - if err != nil { - return nil, err - } - if !isOnboarding { - return nil, &influxdb.Error{ - Code: influxdb.EConflict, - Msg: "onboarding has already been completed", - } - } - - if err := req.Valid(); err != nil { - return nil, err - } - - u := &influxdb.User{Name: req.User} - o := &influxdb.Organization{Name: req.Org} - bucket := &influxdb.Bucket{ - Name: req.Bucket, - RetentionPeriod: req.RetentionPeriod, - } - mapping := &influxdb.UserResourceMapping{ - ResourceType: influxdb.OrgsResourceType, - UserType: influxdb.Owner, - } - auth := &influxdb.Authorization{ - Description: fmt.Sprintf("%s's Token", u.Name), - Permissions: influxdb.OperPermissions(), - Token: req.Token, - } - - var passwordHash []byte - passwordHash, err = s.generatePasswordHash(req.Password) - if err != nil { - return nil, err - } - - err = s.kv.Update(ctx, func(tx Tx) error { - if err := s.createUser(ctx, tx, u); err != nil { - return err - } - - if err := s.setPasswordHashTx(ctx, tx, u.ID, passwordHash); err != nil { - return err - } - - if err := s.createOrganization(ctx, tx, o); err != nil { - return err - } - - bucket.OrgID = o.ID - if err := s.createBucket(ctx, tx, bucket); err != nil { - return err - } - - mapping.ResourceID = o.ID - mapping.UserID = u.ID - if err := s.createUserResourceMapping(ctx, tx, mapping); err != nil { - return err - } - - auth.UserID = u.ID - auth.OrgID = o.ID - if err := s.createAuthorization(ctx, tx, auth); err != nil { - return err - } - - return s.putOnboardingStatus(ctx, tx, true) - }) - if err != nil { - return nil, err - } - - return &influxdb.OnboardingResults{ - User: u, - Org: o, - Bucket: bucket, - Auth: auth, - }, nil -} - -func (s *Service) OnboardUser(ctx context.Context, req *influxdb.OnboardingRequest) (*influxdb.OnboardingResults, error) { - return nil, errors.New("not yet implemented") -} diff --git a/kv/onboarding_test.go b/kv/onboarding_test.go deleted file mode 100644 index ee2d124730..0000000000 --- a/kv/onboarding_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - influxdb "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltOnboardingService(t *testing.T) { - influxdbtesting.OnboardInitialUser(initBoltOnboardingService, t) -} - -func initBoltOnboardingService(f influxdbtesting.OnboardingFields, t *testing.T) (influxdb.OnboardingService, func()) { - s, closeStore, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new bolt kv store: %v", err) - } - - svc, closeSvc := initOnboardingService(s, f, t) - return svc, func() { - closeSvc() - closeStore() - } -} - -func initOnboardingService(s kv.SchemaStore, f influxdbtesting.OnboardingFields, t *testing.T) (influxdb.OnboardingService, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.IDGenerator = f.IDGenerator - svc.OrgIDs = f.IDGenerator - svc.BucketIDs = f.IDGenerator - svc.TokenGenerator = f.TokenGenerator - svc.TimeGenerator = f.TimeGenerator - if f.TimeGenerator == nil { - svc.TimeGenerator = influxdb.RealTimeGenerator{} - } - - t.Logf("Onboarding: %v", f.IsOnboarding) - if err := svc.PutOnboardingStatus(ctx, !f.IsOnboarding); err != nil { - t.Fatalf("failed to set new onboarding finished: %v", err) - } - - return svc, func() { - if err := svc.PutOnboardingStatus(ctx, false); err != nil { - t.Logf("failed to remove onboarding finished: %v", err) - } - } -} diff --git a/kv/org.go b/kv/org.go deleted file mode 100644 index 2859755f67..0000000000 --- a/kv/org.go +++ /dev/null @@ -1,727 +0,0 @@ -package kv - -import ( - "context" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/influxdata/influxdb/v2/resource" - "go.uber.org/zap" - - "github.com/influxdata/influxdb/v2" - icontext "github.com/influxdata/influxdb/v2/context" - "github.com/influxdata/influxdb/v2/kit/tracing" -) - -const ( - // MaxIDGenerationN is the maximum number of times an ID generation is done before failing. - MaxIDGenerationN = 100 -) - -var ( - organizationBucket = []byte("organizationsv1") - organizationIndex = []byte("organizationindexv1") -) - -// ErrFailureGeneratingID occurs ony when the random number generator -// cannot generate an ID in MaxIDGenerationN times. -var ErrFailureGeneratingID = &influxdb.Error{ - Code: influxdb.EInternal, - Msg: "unable to generate valid id", -} - -var _ influxdb.OrganizationService = (*Service)(nil) -var _ influxdb.OrganizationOperationLogService = (*Service)(nil) - -// FindOrganizationByID retrieves a organization by id. -func (s *Service) FindOrganizationByID(ctx context.Context, id influxdb.ID) (*influxdb.Organization, error) { - var o *influxdb.Organization - err := s.kv.View(ctx, func(tx Tx) error { - org, pe := s.findOrganizationByID(ctx, tx, id) - if pe != nil { - return &influxdb.Error{ - Err: pe, - } - } - o = org - return nil - }) - - if err != nil { - return nil, err - } - - return o, nil -} - -func (s *Service) findOrganizationByID(ctx context.Context, tx Tx, id influxdb.ID) (*influxdb.Organization, error) { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - encodedID, err := id.Encode() - if err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - b, err := tx.Bucket(organizationBucket) - if err != nil { - return nil, err - } - - v, err := b.Get(encodedID) - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "organization not found", - } - } - - if err != nil { - return nil, err - } - - var o influxdb.Organization - if err := json.Unmarshal(v, &o); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return &o, nil -} - -// FindOrganizationByName returns a organization by name for a particular organization. -func (s *Service) FindOrganizationByName(ctx context.Context, n string) (*influxdb.Organization, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var o *influxdb.Organization - - err := s.kv.View(ctx, func(tx Tx) error { - org, err := s.findOrganizationByName(ctx, tx, n) - if err != nil { - return err - } - o = org - return nil - }) - - return o, err -} - -func (s *Service) findOrganizationByName(ctx context.Context, tx Tx, n string) (*influxdb.Organization, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - b, err := tx.Bucket(organizationIndex) - if err != nil { - return nil, err - } - - o, err := b.Get(organizationIndexKey(n)) - if IsNotFound(err) { - return nil, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: fmt.Sprintf("organization name \"%s\" not found", n), - } - } - - if err != nil { - return nil, err - } - - var id influxdb.ID - if err := id.Decode(o); err != nil { - return nil, &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - return s.findOrganizationByID(ctx, tx, id) -} - -// FindOrganization retrieves a organization using an arbitrary organization filter. -// Filters using ID, or Name should be efficient. -// Other filters will do a linear scan across organizations until it finds a match. -func (s *Service) FindOrganization(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - var o *influxdb.Organization - - err := s.kv.View(ctx, func(tx Tx) error { - org, err := s.findOrganization(ctx, tx, filter) - if err != nil { - return err - } - o = org - return nil - }) - - return o, err - -} - -func (s *Service) findOrganization(ctx context.Context, tx Tx, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) { - if filter.ID != nil { - return s.findOrganizationByID(ctx, tx, *filter.ID) - } - - if filter.Name != nil { - return s.findOrganizationByName(ctx, tx, *filter.Name) - } - - // If name and ID are not set, then, this is an invalid usage of the API. - return nil, influxdb.ErrInvalidOrgFilter -} - -func filterOrganizationsFn(filter influxdb.OrganizationFilter) func(o *influxdb.Organization) bool { - if filter.ID != nil { - return func(o *influxdb.Organization) bool { - return o.ID == *filter.ID - } - } - - if filter.Name != nil { - return func(o *influxdb.Organization) bool { - return o.Name == *filter.Name - } - } - - return func(o *influxdb.Organization) bool { return true } -} - -// FindOrganizations retrieves all organizations that match an arbitrary organization filter. -// Filters using ID, or Name should be efficient. -// Other filters will do a linear scan across all organizations searching for a match. -func (s *Service) FindOrganizations(ctx context.Context, filter influxdb.OrganizationFilter, opt ...influxdb.FindOptions) ([]*influxdb.Organization, int, error) { - if filter.ID != nil { - o, err := s.FindOrganizationByID(ctx, *filter.ID) - if err != nil { - return nil, 0, &influxdb.Error{ - Err: err, - } - } - - return []*influxdb.Organization{o}, 1, nil - } - - if filter.Name != nil { - o, err := s.FindOrganizationByName(ctx, *filter.Name) - if err != nil { - return nil, 0, &influxdb.Error{ - Err: err, - } - } - - return []*influxdb.Organization{o}, 1, nil - } - - var offset, limit, count int - var descending bool - if len(opt) > 0 { - offset = opt[0].Offset - limit = opt[0].Limit - descending = opt[0].Descending - } - - os := []*influxdb.Organization{} - - if filter.UserID != nil { - // find urms for orgs with this user - urms, _, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{ - UserID: *filter.UserID, - ResourceType: influxdb.OrgsResourceType, - }) - - if err != nil { - return nil, 0, err - } - // find orgs by the urm's resource ids. - for _, urm := range urms { - o, err := s.FindOrganizationByID(ctx, urm.ResourceID) - if err == nil { - // if there is an error then this is a crufty urm and we should just move on - if count >= offset { - os = append(os, o) - } - count++ - } - if limit > 0 && len(os) >= limit { - break - } - } - if descending { - for i, j := 0, len(os)-1; i < j; i, j = i+1, j-1 { - os[i], os[j] = os[j], os[i] - } - } - - return os, len(os), nil - } - - filterFn := filterOrganizationsFn(filter) - err := s.kv.View(ctx, func(tx Tx) error { - return forEachOrganization(ctx, tx, descending, func(o *influxdb.Organization) bool { - if filterFn(o) { // TODO: Currently filterFn is useless here as we have finished all filtering jobs before. Keep it for future changes. - if count >= offset { - os = append(os, o) - } - count++ - } - if limit > 0 && len(os) >= limit { - return false - } - return true - }) - }) - - if err != nil { - return nil, 0, &influxdb.Error{ - Err: err, - } - } - - return os, len(os), nil -} - -// CreateOrganization creates a influxdb organization and sets b.ID. -func (s *Service) CreateOrganization(ctx context.Context, o *influxdb.Organization) error { - return s.kv.Update(ctx, func(tx Tx) error { - if err := s.createOrganization(ctx, tx, o); err != nil { - return err - } - - // Attempt to add user as owner of organization, if that is not possible allow the - // organization to be created anyways. - if err := s.addOrgOwner(ctx, tx, o.ID); err != nil { - s.log.Info("Failed to make user owner of organization", zap.Error(err)) - } - - return s.createSystemBuckets(ctx, tx, o) - }) -} - -// addOrgOwner attempts to create a user resource mapping for the user on the -// authorizer found on context. If no authorizer is found on context if returns an error. -func (s *Service) addOrgOwner(ctx context.Context, tx Tx, orgID influxdb.ID) error { - return s.addResourceOwner(ctx, tx, influxdb.OrgsResourceType, orgID) -} - -// CreateOrganizationTx is used when importing kv as a library -func (s *Service) CreateOrganizationTx(ctx context.Context, tx Tx, o *influxdb.Organization) (err error) { - return s.createOrganization(ctx, tx, o) -} - -func (s *Service) createOrganization(ctx context.Context, tx Tx, o *influxdb.Organization) (err error) { - if err := s.validOrganizationName(ctx, tx, o); err != nil { - return err - } - - if o.ID, err = s.generateOrgID(ctx, tx); err != nil { - return err - } - o.CreatedAt = s.Now() - o.UpdatedAt = s.Now() - if err := s.appendOrganizationEventToLog(ctx, tx, o.ID, organizationCreatedEvent); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - v, err := json.Marshal(o) - if err != nil { - return influxdb.ErrInternalOrgServiceError(influxdb.OpCreateOrganization, err) - } - if err := s.putOrganization(ctx, tx, o, v); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - uid, _ := icontext.GetUserID(ctx) - return s.audit.Log(resource.Change{ - Type: resource.Create, - ResourceID: o.ID, - ResourceType: influxdb.OrgsResourceType, - OrganizationID: o.ID, - UserID: uid, - ResourceBody: v, - Time: time.Now(), - }) -} - -func (s *Service) generateOrgID(ctx context.Context, tx Tx) (influxdb.ID, error) { - return s.generateSafeID(ctx, tx, organizationBucket, s.OrgIDs) -} - -// PutOrganization will put a organization without setting an ID. -func (s *Service) PutOrganization(ctx context.Context, o *influxdb.Organization) error { - return s.kv.Update(ctx, func(tx Tx) error { - v, err := json.Marshal(o) - if err != nil { - return influxdb.ErrInternalOrgServiceError(influxdb.OpPutOrganization, err) - } - - if err := s.putOrganization(ctx, tx, o, v); err != nil { - return err - } - - uid, _ := icontext.GetUserID(ctx) - return s.audit.Log(resource.Change{ - Type: resource.Put, - ResourceID: o.ID, - ResourceType: influxdb.OrgsResourceType, - OrganizationID: o.ID, - UserID: uid, - ResourceBody: v, - Time: time.Now(), - }) - }) -} - -func (s *Service) putOrganization(ctx context.Context, tx Tx, o *influxdb.Organization, v []byte) error { - encodedID, err := o.ID.Encode() - if err != nil { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - idx, err := tx.Bucket(organizationIndex) - if err != nil { - return err - } - - if err := idx.Put(organizationIndexKey(o.Name), encodedID); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - b, err := tx.Bucket(organizationBucket) - if err != nil { - return err - } - - if err = b.Put(encodedID, v); err != nil { - return &influxdb.Error{ - Err: err, - } - } - return nil -} - -func organizationIndexKey(n string) []byte { - return []byte(n) -} - -// forEachOrganization will iterate through all organizations while fn returns true. -func forEachOrganization(ctx context.Context, tx Tx, descending bool, fn func(*influxdb.Organization) bool) error { - b, err := tx.Bucket(organizationBucket) - if err != nil { - return err - } - - direction := CursorAscending - if descending { - direction = CursorDescending - } - - cur, err := b.ForwardCursor(nil, WithCursorDirection(direction)) - if err != nil { - return err - } - - for k, v := cur.Next(); k != nil; k, v = cur.Next() { - o := &influxdb.Organization{} - if err := json.Unmarshal(v, o); err != nil { - return err - } - if !fn(o) { - break - } - } - - return nil -} - -func (s *Service) validOrganizationName(ctx context.Context, tx Tx, o *influxdb.Organization) error { - if o.Name = strings.TrimSpace(o.Name); o.Name == "" { - return influxdb.ErrOrgNameisEmpty - } - key := organizationIndexKey(o.Name) - - // if the name is not unique across all organizations, then, do not - // allow creation. - err := s.unique(ctx, tx, organizationIndex, key) - if err == NotUniqueError { - return OrgAlreadyExistsError(o) - } - return err -} - -// UpdateOrganization updates a organization according the parameters set on upd. -func (s *Service) UpdateOrganization(ctx context.Context, id influxdb.ID, upd influxdb.OrganizationUpdate) (*influxdb.Organization, error) { - var o *influxdb.Organization - err := s.kv.Update(ctx, func(tx Tx) error { - org, pe := s.updateOrganization(ctx, tx, id, upd) - if pe != nil { - return &influxdb.Error{ - Err: pe, - } - } - o = org - return nil - }) - - return o, err -} - -func (s *Service) updateOrganization(ctx context.Context, tx Tx, id influxdb.ID, upd influxdb.OrganizationUpdate) (*influxdb.Organization, error) { - o, pe := s.findOrganizationByID(ctx, tx, id) - if pe != nil { - return nil, pe - } - - if upd.Name != nil { - // Organizations are indexed by name and so the organization index must be pruned - // when name is modified. - idx, err := tx.Bucket(organizationIndex) - if err != nil { - return nil, err - } - if err := idx.Delete(organizationIndexKey(o.Name)); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - o.Name = *upd.Name - if err := s.validOrganizationName(ctx, tx, o); err != nil { - return nil, err - } - } - - if upd.Description != nil { - o.Description = *upd.Description - } - - o.UpdatedAt = s.Now() - - if err := s.appendOrganizationEventToLog(ctx, tx, o.ID, organizationUpdatedEvent); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - v, err := json.Marshal(o) - if err != nil { - return nil, influxdb.ErrInternalOrgServiceError(influxdb.OpUpdateOrganization, err) - } - if pe := s.putOrganization(ctx, tx, o, v); pe != nil { - return nil, pe - } - - uid, _ := icontext.GetUserID(ctx) - if err := s.audit.Log(resource.Change{ - Type: resource.Update, - ResourceID: o.ID, - ResourceType: influxdb.OrgsResourceType, - OrganizationID: o.ID, - UserID: uid, - ResourceBody: v, - Time: time.Now(), - }); err != nil { - return nil, &influxdb.Error{ - Err: err, - } - } - - return o, nil -} - -func (s *Service) deleteOrganizationsBuckets(ctx context.Context, tx Tx, id influxdb.ID) error { - filter := influxdb.BucketFilter{ - OrganizationID: &id, - } - bs, err := s.findBuckets(ctx, tx, filter) - if err != nil { - return err - } - for _, b := range bs { - if err := s.deleteBucket(ctx, tx, b.ID); err != nil { - s.log.Warn("Bucket was not deleted", zap.Stringer("bucketID", b.ID), zap.Stringer("orgID", b.OrgID)) - } - } - return nil -} - -// DeleteOrganization deletes a organization and prunes it from the index. -func (s *Service) DeleteOrganization(ctx context.Context, id influxdb.ID) error { - err := s.kv.Update(ctx, func(tx Tx) error { - if err := s.deleteOrganizationsBuckets(ctx, tx, id); err != nil { - return err - } - if pe := s.deleteOrganization(ctx, tx, id); pe != nil { - return pe - } - - uid, _ := icontext.GetUserID(ctx) - return s.audit.Log(resource.Change{ - Type: resource.Delete, - ResourceID: id, - ResourceType: influxdb.OrgsResourceType, - OrganizationID: id, - UserID: uid, - Time: time.Now(), - }) - }) - if err != nil { - return &influxdb.Error{ - Err: err, - } - } - return nil -} - -func (s *Service) deleteOrganization(ctx context.Context, tx Tx, id influxdb.ID) error { - o, pe := s.findOrganizationByID(ctx, tx, id) - if pe != nil { - return pe - } - - idx, err := tx.Bucket(organizationIndex) - if err != nil { - return err - } - - if err := idx.Delete(organizationIndexKey(o.Name)); err != nil { - return &influxdb.Error{ - Err: err, - } - } - encodedID, err := id.Encode() - if err != nil { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - b, err := tx.Bucket(organizationBucket) - if err != nil { - return err - } - - if err = b.Delete(encodedID); err != nil { - return &influxdb.Error{ - Err: err, - } - } - - return nil -} - -//func (s *Service) deleteOrganizationsBuckets(ctx context.Context, tx Tx, id influxdb.ID) error { -// filter := influxdb.BucketFilter{ -// OrganizationID: &id, -// } -// bs, pe := s.findBuckets(ctx, tx, filter) -// if pe != nil { -// return pe -// } -// for _, b := range bs { -// if pe := s.deleteBucket(ctx, tx, b.ID); pe != nil { -// return pe -// } -// } -// return nil -//} - -// GetOrganizationOperationLog retrieves a organization operation log. -func (s *Service) GetOrganizationOperationLog(ctx context.Context, id influxdb.ID, opts influxdb.FindOptions) ([]*influxdb.OperationLogEntry, int, error) { - // TODO(desa): might be worthwhile to allocate a slice of size opts.Limit - log := []*influxdb.OperationLogEntry{} - - err := s.kv.View(ctx, func(tx Tx) error { - key, err := encodeOrganizationOperationLogKey(id) - if err != nil { - return err - } - - return s.ForEachLogEntryTx(ctx, tx, key, opts, func(v []byte, t time.Time) error { - e := &influxdb.OperationLogEntry{} - if err := json.Unmarshal(v, e); err != nil { - return err - } - e.Time = t - - log = append(log, e) - - return nil - }) - }) - - if err != nil && err != ErrKeyValueLogBoundsNotFound { - return nil, 0, err - } - - return log, len(log), nil -} - -// TODO(desa): what do we want these to be? -const ( - organizationCreatedEvent = "Organization Created" - organizationUpdatedEvent = "Organization Updated" -) - -const orgOperationLogKeyPrefix = "org" - -func encodeOrganizationOperationLogKey(id influxdb.ID) ([]byte, error) { - buf, err := id.Encode() - if err != nil { - return nil, err - } - return append([]byte(orgOperationLogKeyPrefix), buf...), nil -} - -func (s *Service) appendOrganizationEventToLog(ctx context.Context, tx Tx, id influxdb.ID, st string) error { - e := &influxdb.OperationLogEntry{ - Description: st, - } - // TODO(desa): this is fragile and non explicit since it requires an authorizer to be on context. It should be - // replaced with a higher level transaction so that adding to the log can take place in the http handler - // where the organizationID will exist explicitly. - a, err := icontext.GetAuthorizer(ctx) - if err == nil { - // Add the organization to the log if you can, but don't error if its not there. - e.UserID = a.GetUserID() - } - - v, err := json.Marshal(e) - if err != nil { - return err - } - - k, err := encodeOrganizationOperationLogKey(id) - if err != nil { - return err - } - - return s.AddLogEntryTx(ctx, tx, k, v, s.Now()) -} - -// OrgAlreadyExistsError is used when creating a new organization with -// a name that has already been used. Organization names must be unique. -func OrgAlreadyExistsError(o *influxdb.Organization) error { - return &influxdb.Error{ - Code: influxdb.EConflict, - Msg: fmt.Sprintf("organization with name %s already exists", o.Name), - } -} diff --git a/kv/org_test.go b/kv/org_test.go deleted file mode 100644 index 5b2b0c7698..0000000000 --- a/kv/org_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltOrganizationService(t *testing.T) { - influxdbtesting.OrganizationService(initBoltOrganizationService, t) -} - -func initBoltOrganizationService(f influxdbtesting.OrganizationFields, t *testing.T) (influxdb.OrganizationService, string, func()) { - s, closeBolt, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new kv store: %v", err) - } - - svc, op, closeSvc := initOrganizationService(s, f, t) - return svc, op, func() { - closeSvc() - closeBolt() - } -} - -func initOrganizationService(s kv.SchemaStore, f influxdbtesting.OrganizationFields, t *testing.T) (influxdb.OrganizationService, string, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.OrgIDs = f.OrgBucketIDs - svc.BucketIDs = f.OrgBucketIDs - svc.IDGenerator = f.IDGenerator - svc.TimeGenerator = f.TimeGenerator - if f.TimeGenerator == nil { - svc.TimeGenerator = influxdb.RealTimeGenerator{} - } - - for _, o := range f.Organizations { - o.ID = svc.OrgIDs.ID() - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate organizations: %s", err) - } - } - - return svc, kv.OpPrefix, func() { - for _, u := range f.Organizations { - if err := svc.DeleteOrganization(ctx, u.ID); err != nil { - t.Logf("failed to remove organizations: %v", err) - } - } - } -} diff --git a/kv/passwords.go b/kv/passwords.go deleted file mode 100644 index 8e4e340071..0000000000 --- a/kv/passwords.go +++ /dev/null @@ -1,229 +0,0 @@ -package kv - -import ( - "context" - "fmt" - - "golang.org/x/crypto/bcrypt" - - "github.com/influxdata/influxdb/v2" -) - -// MinPasswordLength is the shortest password we allow into the system. -const MinPasswordLength = 8 - -var ( - // EIncorrectPassword is returned when any password operation fails in which - // we do not want to leak information. - EIncorrectPassword = &influxdb.Error{ - Code: influxdb.EForbidden, - Msg: "your username or password is incorrect", - } - - // EIncorrectUser is returned when any user is failed to be found which indicates - // the userID provided is for a user that does not exist. - EIncorrectUser = &influxdb.Error{ - Code: influxdb.EForbidden, - Msg: "your userID is incorrect", - } - - // EShortPassword is used when a password is less than the minimum - // acceptable password length. - EShortPassword = &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "passwords must be at least 8 characters long", - } -) - -// UnavailablePasswordServiceError is used if we aren't able to add the -// password to the store, it means the store is not available at the moment -// (e.g. network). -func UnavailablePasswordServiceError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EUnavailable, - Msg: fmt.Sprintf("Unable to connect to password service. Please try again; Err: %v", err), - Op: "kv/setPassword", - } -} - -// CorruptUserIDError is used when the ID was encoded incorrectly previously. -// This is some sort of internal server error. -func CorruptUserIDError(userID string, err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("User ID %s has been corrupted; Err: %v", userID, err), - Op: "kv/setPassword", - } -} - -// InternalPasswordHashError is used if the hasher is unable to generate -// a hash of the password. This is some sort of internal server error. -func InternalPasswordHashError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("Unable to generate password; Err: %v", err), - Op: "kv/setPassword", - } -} - -var ( - userpasswordBucket = []byte("userspasswordv1") -) - -var _ influxdb.PasswordsService = (*Service)(nil) - -// CompareAndSetPassword checks the password and if they match -// updates to the new password. -func (s *Service) CompareAndSetPassword(ctx context.Context, userID influxdb.ID, old string, new string) error { - newHash, err := s.generatePasswordHash(new) - if err != nil { - return err - } - - if err := s.compareUserPassword(ctx, userID, old); err != nil { - return err - } - - return s.setPasswordHash(ctx, userID, newHash) -} - -// SetPassword overrides the password of a known user. -func (s *Service) SetPassword(ctx context.Context, userID influxdb.ID, password string) error { - hash, err := s.generatePasswordHash(password) - if err != nil { - return err - } - - return s.setPasswordHash(ctx, userID, hash) -} - -// ComparePassword checks if the password matches the password recorded. -// Passwords that do not match return errors. -func (s *Service) ComparePassword(ctx context.Context, userID influxdb.ID, password string) error { - return s.compareUserPassword(ctx, userID, password) -} - -func (s *Service) getPasswordHash(ctx context.Context, userID influxdb.ID) ([]byte, error) { - var passwordHash []byte - err := s.kv.View(ctx, func(tx Tx) error { - var err error - encodedID, err := userID.Encode() - if err != nil { - return CorruptUserIDError(userID.String(), err) - } - - if _, err := s.findUserByID(ctx, tx, userID); err != nil { - return EIncorrectUser - } - - b, err := tx.Bucket(userpasswordBucket) - if err != nil { - return UnavailablePasswordServiceError(err) - } - - passwordHash, err = b.Get(encodedID) - if err != nil { - return EIncorrectPassword - } - - return nil - }) - - return passwordHash, err -} - -func (s *Service) compareUserPassword(ctx context.Context, userID influxdb.ID, password string) error { - passwordHash, err := s.getPasswordHash(ctx, userID) - if err != nil { - return err - } - - hasher := s.Hash - if hasher == nil { - hasher = &Bcrypt{} - } - - if err := hasher.CompareHashAndPassword(passwordHash, []byte(password)); err != nil { - return EIncorrectPassword - } - - return nil -} - -func (s *Service) setPasswordHash(ctx context.Context, userID influxdb.ID, hash []byte) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.setPasswordHashTx(ctx, tx, userID, hash) - }) -} - -func (s *Service) setPasswordHashTx(ctx context.Context, tx Tx, userID influxdb.ID, hash []byte) error { - encodedID, err := userID.Encode() - if err != nil { - return CorruptUserIDError(userID.String(), err) - } - - if _, err := s.findUserByID(ctx, tx, userID); err != nil { - return EIncorrectUser - } - - b, err := tx.Bucket(userpasswordBucket) - if err != nil { - return UnavailablePasswordServiceError(err) - } - - if err := b.Put(encodedID, hash); err != nil { - return UnavailablePasswordServiceError(err) - } - - return nil -} - -func (s *Service) generatePasswordHash(password string) ([]byte, error) { - if len(password) < MinPasswordLength { - return nil, EShortPassword - } - - hasher := s.Hash - if hasher == nil { - hasher = &Bcrypt{} - } - hash, err := hasher.GenerateFromPassword([]byte(password), DefaultCost) - if err != nil { - return nil, InternalPasswordHashError(err) - } - return hash, nil -} - -// DefaultCost is the cost that will actually be set if a cost below MinCost -// is passed into GenerateFromPassword -var DefaultCost = bcrypt.DefaultCost - -// Crypt represents a cryptographic hashing function. -type Crypt interface { - // CompareHashAndPassword compares a hashed password with its possible plaintext equivalent. - // Returns nil on success, or an error on failure. - CompareHashAndPassword(hashedPassword, password []byte) error - // GenerateFromPassword returns the hash of the password at the given cost. - // If the cost given is less than MinCost, the cost will be set to DefaultCost, instead. - GenerateFromPassword(password []byte, cost int) ([]byte, error) -} - -var _ Crypt = (*Bcrypt)(nil) - -// Bcrypt implements Crypt using golang.org/x/crypto/bcrypt -type Bcrypt struct{} - -// CompareHashAndPassword compares a hashed password with its possible plaintext equivalent. -// Returns nil on success, or an error on failure. -func (b *Bcrypt) CompareHashAndPassword(hashedPassword, password []byte) error { - return bcrypt.CompareHashAndPassword(hashedPassword, password) -} - -// GenerateFromPassword returns the hash of the password at the given cost. -// If the cost given is less than MinCost, the cost will be set to DefaultCost, instead. -func (b *Bcrypt) GenerateFromPassword(password []byte, cost int) ([]byte, error) { - if cost < bcrypt.MinCost { - cost = DefaultCost - } - return bcrypt.GenerateFromPassword(password, cost) -} diff --git a/kv/passwords_test.go b/kv/passwords_test.go deleted file mode 100644 index 20b1836b05..0000000000 --- a/kv/passwords_test.go +++ /dev/null @@ -1,479 +0,0 @@ -package kv_test - -import ( - "context" - "errors" - "fmt" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/mock" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltPasswordService(t *testing.T) { - influxdbtesting.PasswordsService(initBoltPasswordsService, t) -} - -func initBoltPasswordsService(f influxdbtesting.PasswordFields, t *testing.T) (influxdb.PasswordsService, func()) { - s, closeStore, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new bolt kv store: %v", err) - } - - svc, closeSvc := initPasswordsService(s, f, t) - return svc, func() { - closeSvc() - closeStore() - } -} - -func initPasswordsService(s kv.SchemaStore, f influxdbtesting.PasswordFields, t *testing.T) (influxdb.PasswordsService, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - - svc.IDGenerator = f.IDGenerator - - for _, u := range f.Users { - if err := svc.PutUser(ctx, u); err != nil { - t.Fatalf("error populating users: %v", err) - } - } - - for i := range f.Passwords { - if err := svc.SetPassword(ctx, f.Users[i].ID, f.Passwords[i]); err != nil { - t.Fatalf("error setting passsword user, %s %s: %v", f.Users[i].Name, f.Passwords[i], err) - } - } - - return svc, func() { - for _, u := range f.Users { - if err := svc.DeleteUser(ctx, u.ID); err != nil { - t.Logf("error removing users: %v", err) - } - } - } -} - -type MockHasher struct { - GenerateError error - CompareError error -} - -func (m *MockHasher) CompareHashAndPassword(hashedPassword, password []byte) error { - return m.CompareError -} - -func (m *MockHasher) GenerateFromPassword(password []byte, cost int) ([]byte, error) { - return nil, m.GenerateError -} - -func TestService_SetPassword(t *testing.T) { - type fields struct { - kv kv.Store - Hash kv.Crypt - } - type args struct { - id influxdb.ID - password string - } - type wants struct { - err error - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "if store somehow has a corrupted user index, then, we get back an internal error", - fields: fields{ - kv: &mock.Store{ - UpdateFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - return nil, errors.New("its broked") - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - err: fmt.Errorf("your userID is incorrect"), - }, - }, - { - name: "if user id is not found return a generic sounding error", - fields: fields{ - kv: &mock.Store{ - UpdateFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - return nil, kv.ErrKeyNotFound - }, - PutFn: func(key, val []byte) error { - return nil - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - err: fmt.Errorf("your userID is incorrect"), - }, - }, - { - name: "if store somehow has a corrupted user id, then, we get back an internal error", - fields: fields{ - kv: &mock.Store{ - UpdateFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - if string(key) == "0000000000000001" { - return []byte(`{"name": "user1"}`), nil - } - return nil, kv.ErrKeyNotFound - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 0, - password: "howdydoody", - }, - wants: wants{ - err: fmt.Errorf("User ID has been corrupted; Err: invalid ID"), - }, - }, - { - name: "if password store is not available, then, we get back an internal error", - fields: fields{ - kv: &mock.Store{ - UpdateFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - if string(b) == "userspasswordv1" { - return nil, fmt.Errorf("internal bucket error") - } - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - if string(key) == "0000000000000001" { - return []byte(`{"id": "0000000000000001", "name": "user1"}`), nil - } - return nil, kv.ErrKeyNotFound - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - err: fmt.Errorf("Unable to connect to password service. Please try again; Err: internal bucket error"), - }, - }, - { - name: "if hashing algorithm has an error, then, we get back an internal error", - fields: fields{ - Hash: &MockHasher{ - GenerateError: fmt.Errorf("generate error"), - }, - kv: &mock.Store{ - UpdateFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - if string(b) == "userspasswordv1" { - return nil, nil - } - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - if string(key) == "0000000000000001" { - return []byte(`{"id": "0000000000000001", "name": "user1"}`), nil - } - return nil, kv.ErrKeyNotFound - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - fmt.Errorf("Unable to generate password; Err: generate error"), - }, - }, - { - name: "if not able to store the hashed password should have an internal error", - fields: fields{ - kv: &mock.Store{ - UpdateFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - if string(b) == "userspasswordv1" { - return &mock.Bucket{ - PutFn: func(key, value []byte) error { - return fmt.Errorf("internal error") - }, - }, nil - } - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - if string(key) == "0000000000000001" { - return []byte(`{"id": "0000000000000001", "name": "user1"}`), nil - } - return nil, kv.ErrKeyNotFound - }, - PutFn: func(key, val []byte) error { - return nil - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - fmt.Errorf("Unable to connect to password service. Please try again; Err: internal error"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &kv.Service{ - Hash: tt.fields.Hash, - } - s.WithStore(tt.fields.kv) - - err := s.SetPassword(context.Background(), tt.args.id, tt.args.password) - if (err != nil && tt.wants.err == nil) || (err == nil && tt.wants.err != nil) { - t.Fatalf("Service.SetPassword() error = %v, want %v", err, tt.wants.err) - return - } - - if err != nil { - if got, want := err.Error(), tt.wants.err.Error(); got != want { - t.Errorf("Service.SetPassword() error = %v, want %v", got, want) - } - } - }) - } -} - -func TestService_ComparePassword(t *testing.T) { - type fields struct { - kv kv.Store - Hash kv.Crypt - } - type args struct { - id influxdb.ID - password string - } - type wants struct { - err error - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "if store somehow has a corrupted user index, then, we get back an internal error", - fields: fields{ - kv: &mock.Store{ - ViewFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - return nil, nil - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - err: fmt.Errorf("your userID is incorrect"), - }, - }, - { - name: "if store somehow has a corrupted user id, then, we get back an internal error", - fields: fields{ - kv: &mock.Store{ - ViewFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - if string(key) == "user1" { - return []byte("0000000000000001"), nil - } - if string(key) == "0000000000000001" { - return []byte(`{"name": "user1"}`), nil - } - return nil, kv.ErrKeyNotFound - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 0, - password: "howdydoody", - }, - wants: wants{ - err: fmt.Errorf("User ID has been corrupted; Err: invalid ID"), - }, - }, - { - name: "if password store is not available, then, we get back an internal error", - fields: fields{ - kv: &mock.Store{ - ViewFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - if string(b) == "userspasswordv1" { - return nil, fmt.Errorf("internal bucket error") - } - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - if string(key) == "user1" { - return []byte("0000000000000001"), nil - } - if string(key) == "0000000000000001" { - return []byte(`{"id": "0000000000000001", "name": "user1"}`), nil - } - return nil, kv.ErrKeyNotFound - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - err: fmt.Errorf("Unable to connect to password service. Please try again; Err: internal bucket error"), - }, - }, - { - name: "if the password doesn't has correctly we get an invalid password error", - fields: fields{ - Hash: &MockHasher{ - CompareError: fmt.Errorf("generate error"), - }, - kv: &mock.Store{ - ViewFn: func(fn func(kv.Tx) error) error { - tx := &mock.Tx{ - BucketFn: func(b []byte) (kv.Bucket, error) { - if string(b) == "userspasswordv1" { - return &mock.Bucket{ - GetFn: func([]byte) ([]byte, error) { - return []byte("hash"), nil - }, - }, nil - } - return &mock.Bucket{ - GetFn: func(key []byte) ([]byte, error) { - if string(key) == "user1" { - return []byte("0000000000000001"), nil - } - if string(key) == "0000000000000001" { - return []byte(`{"id": "0000000000000001", "name": "user1"}`), nil - } - return nil, kv.ErrKeyNotFound - }, - }, nil - }, - } - return fn(tx) - }, - }, - }, - args: args{ - id: 1, - password: "howdydoody", - }, - wants: wants{ - fmt.Errorf("your username or password is incorrect"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &kv.Service{ - Hash: tt.fields.Hash, - } - s.WithStore(tt.fields.kv) - err := s.ComparePassword(context.Background(), tt.args.id, tt.args.password) - - if (err != nil && tt.wants.err == nil) || (err == nil && tt.wants.err != nil) { - t.Fatalf("Service.ComparePassword() error = %v, want %v", err, tt.wants.err) - return - } - - if err != nil { - if got, want := err.Error(), tt.wants.err.Error(); got != want { - t.Errorf("Service.ComparePassword() error = %v, want %v", got, want) - } - } - }) - } -} diff --git a/kv/scrapers.go b/kv/scrapers.go index 95a271a63e..d70a94c4d8 100644 --- a/kv/scrapers.go +++ b/kv/scrapers.go @@ -92,6 +92,17 @@ func (s *Service) scrapersBucket(tx Tx) (Bucket, error) { // ListTargets will list all scrape targets. func (s *Service) ListTargets(ctx context.Context, filter influxdb.ScraperTargetFilter) ([]influxdb.ScraperTarget, error) { + if filter.Org != nil { + org, err := s.orgs.FindOrganization(ctx, influxdb.OrganizationFilter{ + Name: filter.Org, + }) + if err != nil { + return nil, err + } + + filter.OrgID = &org.ID + } + targets := []influxdb.ScraperTarget{} err := s.kv.View(ctx, func(tx Tx) error { var err error @@ -113,21 +124,6 @@ func (s *Service) listTargets(ctx context.Context, tx Tx, filter influxdb.Scrape return nil, UnexpectedScrapersBucketError(err) } - var org *influxdb.Organization - if filter.Org != nil { - org, err = s.findOrganizationByName(ctx, tx, *filter.Org) - if err != nil { - return nil, err - } - } - - if filter.OrgID != nil { - org, err = s.findOrganizationByID(ctx, tx, *filter.OrgID) - if err != nil { - return nil, err - } - } - for k, v := cur.Next(); k != nil; k, v = cur.Next() { target, err := unmarshalScraper(v) if err != nil { @@ -142,7 +138,7 @@ func (s *Service) listTargets(ctx context.Context, tx Tx, filter influxdb.Scrape continue } - if org != nil && target.OrgID != org.ID { + if filter.OrgID != nil && target.OrgID != *filter.OrgID { continue } diff --git a/kv/scrapers_test.go b/kv/scrapers_test.go index 7317fdc03f..59bcfee3ed 100644 --- a/kv/scrapers_test.go +++ b/kv/scrapers_test.go @@ -6,6 +6,8 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2/mock" + "github.com/influxdata/influxdb/v2/tenant" influxdbtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -29,8 +31,14 @@ func initBoltTargetService(f influxdbtesting.TargetFields, t *testing.T) (influx func initScraperTargetStoreService(s kv.SchemaStore, f influxdbtesting.TargetFields, t *testing.T) (influxdb.ScraperTargetStoreService, string, func()) { ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.IDGenerator = f.IDGenerator + tenantStore := tenant.NewStore(s) + tenantSvc := tenant.NewService(tenantStore) + + svc := kv.NewService(zaptest.NewLogger(t), s, tenantSvc) + + if f.IDGenerator != nil { + svc.IDGenerator = f.IDGenerator + } for _, target := range f.Targets { if err := svc.PutTarget(ctx, target); err != nil { @@ -39,9 +47,11 @@ func initScraperTargetStoreService(s kv.SchemaStore, f influxdbtesting.TargetFie } for _, o := range f.Organizations { - if err := svc.PutOrganization(ctx, o); err != nil { - t.Fatalf("failed to populate organization") - } + mock.SetIDForFunc(&tenantStore.OrgIDGen, o.ID, func() { + if err := tenantSvc.CreateOrganization(ctx, o); err != nil { + t.Fatalf("failed to populate organization") + } + }) } return svc, kv.OpPrefix, func() { @@ -51,7 +61,7 @@ func initScraperTargetStoreService(s kv.SchemaStore, f influxdbtesting.TargetFie } } for _, o := range f.Organizations { - if err := svc.DeleteOrganization(ctx, o.ID); err != nil { + if err := tenantSvc.DeleteOrganization(ctx, o.ID); err != nil { t.Logf("failed to remove orgs: %v", err) } } diff --git a/kv/secret.go b/kv/secret.go deleted file mode 100644 index 95ebd65715..0000000000 --- a/kv/secret.go +++ /dev/null @@ -1,255 +0,0 @@ -package kv - -import ( - "context" - "encoding/base64" - "errors" - - "github.com/influxdata/influxdb/v2" -) - -var ( - secretBucket = []byte("secretsv1") - - _ influxdb.SecretService = (*Service)(nil) -) - -// LoadSecret retrieves the secret value v found at key k for organization orgID. -func (s *Service) LoadSecret(ctx context.Context, orgID influxdb.ID, k string) (string, error) { - var v string - err := s.kv.View(ctx, func(tx Tx) error { - val, err := s.loadSecret(ctx, tx, orgID, k) - if err != nil { - return err - } - - v = val - return nil - }) - - if err != nil { - return "", err - } - - return v, nil -} - -func (s *Service) loadSecret(ctx context.Context, tx Tx, orgID influxdb.ID, k string) (string, error) { - key, err := encodeSecretKey(orgID, k) - if err != nil { - return "", err - } - - b, err := tx.Bucket(secretBucket) - if err != nil { - return "", err - } - - val, err := b.Get(key) - if IsNotFound(err) { - return "", &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: influxdb.ErrSecretNotFound, - } - } - - if err != nil { - return "", err - } - - v, err := decodeSecretValue(val) - if err != nil { - return "", err - } - - return v, nil -} - -// GetSecretKeys retrieves all secret keys that are stored for the organization orgID. -func (s *Service) GetSecretKeys(ctx context.Context, orgID influxdb.ID) ([]string, error) { - var vs []string - err := s.kv.View(ctx, func(tx Tx) error { - vals, err := s.getSecretKeys(ctx, tx, orgID) - if err != nil { - return err - } - - vs = vals - return nil - }) - - if err != nil { - return nil, err - } - - return vs, nil -} - -func (s *Service) getSecretKeys(ctx context.Context, tx Tx, orgID influxdb.ID) ([]string, error) { - b, err := tx.Bucket(secretBucket) - if err != nil { - return nil, err - } - - prefix, err := orgID.Encode() - if err != nil { - return nil, err - } - - cur, err := b.ForwardCursor(prefix, WithCursorPrefix(prefix)) - if err != nil { - return nil, err - } - - keys := []string{} - - for k, _ := cur.Next(); k != nil; k, _ = cur.Next() { - - id, key, err := decodeSecretKey(k) - if err != nil { - return nil, err - } - - if id != orgID { - // We've reached the end of the keyspace for the provided orgID - break - } - - keys = append(keys, key) - } - - return keys, nil -} - -// PutSecret stores the secret pair (k,v) for the organization orgID. -func (s *Service) PutSecret(ctx context.Context, orgID influxdb.ID, k, v string) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.putSecret(ctx, tx, orgID, k, v) - }) -} - -func (s *Service) putSecret(ctx context.Context, tx Tx, orgID influxdb.ID, k, v string) error { - key, err := encodeSecretKey(orgID, k) - if err != nil { - return err - } - - val := encodeSecretValue(v) - - b, err := tx.Bucket(secretBucket) - if err != nil { - return err - } - - if err := b.Put(key, val); err != nil { - return err - } - - return nil -} - -func encodeSecretKey(orgID influxdb.ID, k string) ([]byte, error) { - buf, err := orgID.Encode() - if err != nil { - return nil, err - } - - key := make([]byte, 0, influxdb.IDLength+len(k)) - key = append(key, buf...) - key = append(key, k...) - - return key, nil -} - -func decodeSecretKey(key []byte) (influxdb.ID, string, error) { - if len(key) < influxdb.IDLength { - // This should not happen. - return influxdb.InvalidID(), "", errors.New("provided key is too short to contain an ID (please report this error)") - } - - var id influxdb.ID - if err := id.Decode(key[:influxdb.IDLength]); err != nil { - return influxdb.InvalidID(), "", err - } - - k := string(key[influxdb.IDLength:]) - - return id, k, nil -} - -func decodeSecretValue(val []byte) (string, error) { - // store the secret value base64 encoded so that it's marginally better than plaintext - v, err := base64.StdEncoding.DecodeString(string(val)) - if err != nil { - return "", err - } - - return string(v), nil -} - -func encodeSecretValue(v string) []byte { - val := make([]byte, base64.StdEncoding.EncodedLen(len(v))) - base64.StdEncoding.Encode(val, []byte(v)) - return val -} - -// PutSecrets puts all provided secrets and overwrites any previous values. -func (s *Service) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error { - return s.kv.Update(ctx, func(tx Tx) error { - keys, err := s.getSecretKeys(ctx, tx, orgID) - if err != nil { - return err - } - for k, v := range m { - if err := s.putSecret(ctx, tx, orgID, k, v); err != nil { - return err - } - } - for _, k := range keys { - if _, ok := m[k]; !ok { - if err := s.deleteSecret(ctx, tx, orgID, k); err != nil { - return err - } - } - } - return nil - }) -} - -// PatchSecrets patches all provided secrets and updates any previous values. -func (s *Service) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error { - return s.kv.Update(ctx, func(tx Tx) error { - for k, v := range m { - if err := s.putSecret(ctx, tx, orgID, k, v); err != nil { - return err - } - } - return nil - }) -} - -// DeleteSecret removes secrets from the secret store. -func (s *Service) DeleteSecret(ctx context.Context, orgID influxdb.ID, ks ...string) error { - return s.kv.Update(ctx, func(tx Tx) error { - for _, k := range ks { - if err := s.deleteSecret(ctx, tx, orgID, k); err != nil { - return err - } - } - return nil - }) -} - -func (s *Service) deleteSecret(ctx context.Context, tx Tx, orgID influxdb.ID, k string) error { - key, err := encodeSecretKey(orgID, k) - if err != nil { - return err - } - - b, err := tx.Bucket(secretBucket) - if err != nil { - return err - } - - return b.Delete(key) -} diff --git a/kv/secret_test.go b/kv/secret_test.go deleted file mode 100644 index cfc3437e2b..0000000000 --- a/kv/secret_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltSecretService(t *testing.T) { - influxdbtesting.SecretService(initBoltSecretService, t) -} - -func initBoltSecretService(f influxdbtesting.SecretServiceFields, t *testing.T) (influxdb.SecretService, func()) { - s, closeBolt, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new kv store: %v", err) - } - - svc, closeSvc := initSecretService(s, f, t) - return svc, func() { - closeSvc() - closeBolt() - } -} - -func initSecretService(s kv.SchemaStore, f influxdbtesting.SecretServiceFields, t *testing.T) (influxdb.SecretService, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - - for _, s := range f.Secrets { - for k, v := range s.Env { - if err := svc.PutSecret(ctx, s.OrganizationID, k, v); err != nil { - t.Fatalf("failed to populate secrets") - } - } - } - - return svc, func() {} -} diff --git a/kv/service.go b/kv/service.go index e71594d0f4..b317f1e178 100644 --- a/kv/service.go +++ b/kv/service.go @@ -1,8 +1,6 @@ package kv import ( - "time" - "github.com/benbjohnson/clock" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/rand" @@ -12,10 +10,6 @@ import ( "go.uber.org/zap" ) -var ( - _ influxdb.UserService = (*Service)(nil) -) - // OpPrefix is the prefix for kv errors. const OpPrefix = "kv/" @@ -33,51 +27,37 @@ type Service struct { // will fail. FluxLanguageService influxdb.FluxLanguageService - // special ID generator that never returns bytes with backslash, - // comma, or space. Used to support very specific encoding of org & - // bucket into the old measurement in storage. - OrgIDs influxdb.IDGenerator - BucketIDs influxdb.IDGenerator - TokenGenerator influxdb.TokenGenerator // TODO(desa:ariel): this should not be embedded influxdb.TimeGenerator - Hash Crypt + + orgs influxdb.OrganizationService variableStore *IndexStore - - urmByUserIndex *Index } // NewService returns an instance of a Service. -func NewService(log *zap.Logger, kv Store, configs ...ServiceConfig) *Service { +func NewService(log *zap.Logger, kv Store, orgs influxdb.OrganizationService, configs ...ServiceConfig) *Service { s := &Service{ - log: log, - IDGenerator: snowflake.NewIDGenerator(), - // Seed the random number generator with the current time - OrgIDs: rand.NewOrgBucketID(time.Now().UnixNano()), - BucketIDs: rand.NewOrgBucketID(time.Now().UnixNano()), + log: log, + IDGenerator: snowflake.NewIDGenerator(), TokenGenerator: rand.NewTokenGenerator(64), - Hash: &Bcrypt{}, kv: kv, + orgs: orgs, audit: noop.ResourceLogger{}, TimeGenerator: influxdb.RealTimeGenerator{}, variableStore: newVariableStore(), - urmByUserIndex: NewIndex(URMByUserIndexMapping, WithIndexReadPathEnabled), } if len(configs) > 0 { s.Config = configs[0] } - if s.Config.SessionLength == 0 { - s.Config.SessionLength = influxdb.DefaultSessionLength - } - s.clock = s.Config.Clock if s.clock == nil { s.clock = clock.New() } + s.FluxLanguageService = s.Config.FluxLanguageService return s @@ -85,7 +65,6 @@ func NewService(log *zap.Logger, kv Store, configs ...ServiceConfig) *Service { // ServiceConfig allows us to configure Services type ServiceConfig struct { - SessionLength time.Duration Clock clock.Clock FluxLanguageService influxdb.FluxLanguageService } diff --git a/kv/service_test.go b/kv/service_test.go deleted file mode 100644 index 82acb7bfe0..0000000000 --- a/kv/service_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package kv_test - -import ( - "context" - "io" - "testing" - "time" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - "go.uber.org/zap/zaptest" -) - -type mockStore struct { -} - -func (s mockStore) View(context.Context, func(kv.Tx) error) error { - return nil -} - -func (s mockStore) Update(context.Context, func(kv.Tx) error) error { - return nil -} - -func (s mockStore) Backup(ctx context.Context, w io.Writer) error { - return nil -} - -func (s mockStore) Restore(ctx context.Context, r io.Reader) error { - return nil -} - -func TestNewService(t *testing.T) { - s := kv.NewService(zaptest.NewLogger(t), mockStore{}) - - if s.Config.SessionLength != influxdb.DefaultSessionLength { - t.Errorf("Service session length should use default length when not set") - } - - config := kv.ServiceConfig{ - SessionLength: time.Duration(time.Hour * 4), - } - - s = kv.NewService(zaptest.NewLogger(t), mockStore{}, config) - - if s.Config != config { - t.Errorf("Service config not set by constructor") - } -} diff --git a/kv/source_test.go b/kv/source_test.go index cb300f091b..e66f572ac1 100644 --- a/kv/source_test.go +++ b/kv/source_test.go @@ -6,6 +6,7 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2/mock" influxdbtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -32,7 +33,7 @@ func initBoltSourceService(f influxdbtesting.SourceFields, t *testing.T) (influx func initSourceService(s kv.SchemaStore, f influxdbtesting.SourceFields, t *testing.T) (influxdb.SourceService, string, func()) { ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) + svc := kv.NewService(zaptest.NewLogger(t), s, &mock.OrganizationService{}) svc.IDGenerator = f.IDGenerator for _, b := range f.Sources { diff --git a/kv/task.go b/kv/task.go index f9f9be95d7..6a1df1a717 100644 --- a/kv/task.go +++ b/kv/task.go @@ -3,6 +3,8 @@ package kv import ( "context" "encoding/json" + "errors" + "fmt" "strings" "time" @@ -11,7 +13,6 @@ import ( "github.com/influxdata/influxdb/v2/kit/feature" "github.com/influxdata/influxdb/v2/resource" "github.com/influxdata/influxdb/v2/task/options" - "go.uber.org/zap" ) // Task Storage Schema @@ -133,41 +134,23 @@ func (s *Service) findTaskByID(ctx context.Context, tx Tx, id influxdb.ID) (*inf t.LatestCompleted = t.CreatedAt } - // Attempt to fill in the owner ID based on the auth. - if !t.OwnerID.Valid() { - authType := struct { - AuthorizationID influxdb.ID `json:"authorizationID"` - }{} - if err := json.Unmarshal(v, &authType); err != nil { - return nil, influxdb.ErrInternalTaskServiceError(err) - } - - auth, err := s.findAuthorizationByID(ctx, tx, authType.AuthorizationID) - if err == nil { - t.OwnerID = auth.GetUserID() - } - } - - // Attempt to fill in the ownerID based on the organization owners. - // If they have multiple owners just use the first one because any org owner - // will have sufficient permissions to run a task. - if !t.OwnerID.Valid() { - owners, err := s.findUserResourceMappings(ctx, tx, influxdb.UserResourceMappingFilter{ - ResourceID: t.OrganizationID, - ResourceType: influxdb.OrgsResourceType, - UserType: influxdb.Owner, - }) - if err == nil && len(owners) > 0 { - t.OwnerID = owners[0].UserID - } - } - return t, nil } // FindTasks returns a list of tasks that match a filter (limit 100) and the total count // of matching tasks. func (s *Service) FindTasks(ctx context.Context, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) { + if filter.Organization != "" { + org, err := s.orgs.FindOrganization(ctx, influxdb.OrganizationFilter{ + Name: &filter.Organization, + }) + if err != nil { + return nil, 0, err + } + + filter.OrganizationID = &org.ID + } + var ts []*influxdb.Task err := s.kv.View(ctx, func(tx Tx) error { tasks, _, err := s.findTasks(ctx, tx, filter) @@ -185,20 +168,6 @@ func (s *Service) FindTasks(ctx context.Context, filter influxdb.TaskFilter) ([] } func (s *Service) findTasks(ctx context.Context, tx Tx, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) { - var org *influxdb.Organization - var err error - if filter.OrganizationID != nil { - org, err = s.findOrganizationByID(ctx, tx, *filter.OrganizationID) - if err != nil { - return nil, 0, err - } - } else if filter.Organization != "" { - org, err = s.findOrganizationByName(ctx, tx, filter.Organization) - if err != nil { - return nil, 0, err - } - } - // complain about limits if filter.Limit < 0 { return nil, 0, influxdb.ErrPageSizeTooSmall @@ -212,7 +181,7 @@ func (s *Service) findTasks(ctx context.Context, tx Tx, filter influxdb.TaskFilt // if no user or organization is passed, assume contexts auth is the user we are looking for. // it is possible for a internal system to call this with no auth so we shouldnt fail if no auth is found. - if org == nil && filter.User == nil { + if filter.OrganizationID == nil && filter.User == nil { userAuth, err := icontext.GetAuthorizer(ctx) if err == nil { userID := userAuth.GetUserID() @@ -225,8 +194,8 @@ func (s *Service) findTasks(ctx context.Context, tx Tx, filter influxdb.TaskFilt // filter by user id. if filter.User != nil { return s.findTasksByUser(ctx, tx, filter) - } else if org != nil { - return s.findTasksByOrg(ctx, tx, filter) + } else if filter.OrganizationID != nil { + return s.findTasksByOrg(ctx, tx, *filter.OrganizationID, filter) } return s.findAllTasks(ctx, tx, filter) @@ -285,23 +254,10 @@ func (s *Service) findTasksByUser(ctx context.Context, tx Tx, filter influxdb.Ta } // findTasksByOrg is a subset of the find tasks function. Used for cleanliness -func (s *Service) findTasksByOrg(ctx context.Context, tx Tx, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) { - var org *influxdb.Organization +func (s *Service) findTasksByOrg(ctx context.Context, tx Tx, orgID influxdb.ID, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) { var err error - if filter.OrganizationID != nil { - org, err = s.findOrganizationByID(ctx, tx, *filter.OrganizationID) - if err != nil { - return nil, 0, err - } - } else if filter.Organization != "" { - org, err = s.findOrganizationByName(ctx, tx, filter.Organization) - if err != nil { - return nil, 0, err - } - } - - if org == nil { - return nil, 0, influxdb.ErrTaskNotFound + if !orgID.Valid() { + return nil, 0, fmt.Errorf("finding tasks by organization ID: %w", influxdb.ErrInvalidID) } var ts []*influxdb.Task @@ -311,7 +267,7 @@ func (s *Service) findTasksByOrg(ctx context.Context, tx Tx, filter influxdb.Tas return nil, 0, influxdb.ErrUnexpectedTaskBucketErr(err) } - prefix, err := org.ID.Encode() + prefix, err := orgID.Encode() if err != nil { return nil, 0, influxdb.ErrInvalidTaskID } @@ -322,7 +278,7 @@ func (s *Service) findTasksByOrg(ctx context.Context, tx Tx, filter influxdb.Tas ) // we can filter by orgID if filter.After != nil { - key, err = taskOrgKey(org.ID, *filter.After) + key, err = taskOrgKey(orgID, *filter.After) if err != nil { return nil, 0, err } @@ -360,7 +316,7 @@ func (s *Service) findTasksByOrg(ctx context.Context, tx Tx, filter influxdb.Tas } // If the new task doesn't belong to the org we have looped outside the org filter - if org != nil && t.OrganizationID != org.ID { + if t.OrganizationID != orgID { break } @@ -492,40 +448,36 @@ func (s *Service) findAllTasks(ctx context.Context, tx Tx, filter influxdb.TaskF // CreateTask creates a new task. // The owner of the task is inferred from the authorizer associated with ctx. func (s *Service) CreateTask(ctx context.Context, tc influxdb.TaskCreate) (*influxdb.Task, error) { + var orgFilter influxdb.OrganizationFilter + + if tc.Organization != "" { + orgFilter.Name = &tc.Organization + } else if tc.OrganizationID.Valid() { + orgFilter.ID = &tc.OrganizationID + + } else { + return nil, errors.New("organization required") + } + + org, err := s.orgs.FindOrganization(ctx, orgFilter) + if err != nil { + return nil, err + } + var t *influxdb.Task - err := s.kv.Update(ctx, func(tx Tx) error { - task, err := s.createTask(ctx, tx, tc) + err = s.kv.Update(ctx, func(tx Tx) error { + task, err := s.createTask(ctx, tx, org, tc) if err != nil { return err } t = task return nil }) - if err != nil { - return nil, err - } - return t, nil + return t, err } -func (s *Service) createTask(ctx context.Context, tx Tx, tc influxdb.TaskCreate) (*influxdb.Task, error) { - var err error - var org *influxdb.Organization - if tc.OrganizationID.Valid() { - org, err = s.findOrganizationByID(ctx, tx, tc.OrganizationID) - if err != nil { - return nil, err - } - } else if tc.Organization != "" { - org, err = s.findOrganizationByName(ctx, tx, tc.Organization) - if err != nil { - return nil, err - } - } - if org == nil { - return nil, influxdb.ErrOrgNotFound - } - +func (s *Service) createTask(ctx context.Context, tx Tx, org *influxdb.Organization, tc influxdb.TaskCreate) (*influxdb.Task, error) { // TODO: Uncomment this once the checks/notifications no longer create tasks in kv // confirm the owner is a real user. // if _, err = s.findUserByID(ctx, tx, tc.OwnerID); err != nil { @@ -863,12 +815,6 @@ func (s *Service) deleteTask(ctx context.Context, tx Tx, id influxdb.ID) error { return influxdb.ErrUnexpectedTaskBucketErr(err) } - if err := s.deleteUserResourceMapping(ctx, tx, influxdb.UserResourceMappingFilter{ - ResourceID: task.ID, - }); err != nil { - s.log.Debug("Error deleting user resource mapping for task", zap.Stringer("taskID", task.ID), zap.Error(err)) - } - uid, _ := icontext.GetUserID(ctx) return s.audit.Log(resource.Change{ Type: resource.Delete, diff --git a/kv/task_test.go b/kv/task_test.go index 2176017c55..1ac064db72 100644 --- a/kv/task_test.go +++ b/kv/task_test.go @@ -34,11 +34,6 @@ func TestBoltTaskService(t *testing.T) { t.Fatal(err) } - ctx, cancelFunc := context.WithCancel(context.Background()) - service := kv.NewService(zaptest.NewLogger(t), store, kv.ServiceConfig{ - FluxLanguageService: fluxlang.DefaultService, - }) - tenantStore := tenant.NewStore(store) ts := tenant.NewService(tenantStore) @@ -46,6 +41,11 @@ func TestBoltTaskService(t *testing.T) { require.NoError(t, err) authSvc := authorization.NewService(authStore, ts) + ctx, cancelFunc := context.WithCancel(context.Background()) + service := kv.NewService(zaptest.NewLogger(t), store, ts, kv.ServiceConfig{ + FluxLanguageService: fluxlang.DefaultService, + }) + go func() { <-ctx.Done() close() @@ -100,21 +100,28 @@ func newService(t *testing.T, ctx context.Context, c clock.Clock) *testService { ts.Store = store - ts.Service = kv.NewService(zaptest.NewLogger(t), store, kv.ServiceConfig{ + tenantStore := tenant.NewStore(store) + tenantSvc := tenant.NewService(tenantStore) + + authStore, err := authorization.NewStore(store) + require.NoError(t, err) + authSvc := authorization.NewService(authStore, tenantSvc) + + ts.Service = kv.NewService(zaptest.NewLogger(t), store, tenantSvc, kv.ServiceConfig{ Clock: c, FluxLanguageService: fluxlang.DefaultService, }) ts.User = influxdb.User{Name: t.Name() + "-user"} - if err := ts.Service.CreateUser(ctx, &ts.User); err != nil { + if err := tenantSvc.CreateUser(ctx, &ts.User); err != nil { t.Fatal(err) } ts.Org = influxdb.Organization{Name: t.Name() + "-org"} - if err := ts.Service.CreateOrganization(ctx, &ts.Org); err != nil { + if err := tenantSvc.CreateOrganization(ctx, &ts.Org); err != nil { t.Fatal(err) } - if err := ts.Service.CreateUserResourceMapping(ctx, &influxdb.UserResourceMapping{ + if err := tenantSvc.CreateUserResourceMapping(ctx, &influxdb.UserResourceMapping{ ResourceType: influxdb.OrgsResourceType, ResourceID: ts.Org.ID, UserID: ts.User.ID, @@ -128,7 +135,7 @@ func newService(t *testing.T, ctx context.Context, c clock.Clock) *testService { UserID: ts.User.ID, Permissions: influxdb.OperPermissions(), } - if err := ts.Service.CreateAuthorization(context.Background(), &ts.Auth); err != nil { + if err := authSvc.CreateAuthorization(context.Background(), &ts.Auth); err != nil { t.Fatal(err) } @@ -269,20 +276,27 @@ func TestTaskRunCancellation(t *testing.T) { ctx, cancelFunc := context.WithCancel(context.Background()) defer cancelFunc() - service := kv.NewService(zaptest.NewLogger(t), store, kv.ServiceConfig{ + tenantStore := tenant.NewStore(store) + tenantSvc := tenant.NewService(tenantStore) + + authStore, err := authorization.NewStore(store) + require.NoError(t, err) + authSvc := authorization.NewService(authStore, tenantSvc) + + service := kv.NewService(zaptest.NewLogger(t), store, tenantSvc, kv.ServiceConfig{ FluxLanguageService: fluxlang.DefaultService, }) u := &influxdb.User{Name: t.Name() + "-user"} - if err := service.CreateUser(ctx, u); err != nil { + if err := tenantSvc.CreateUser(ctx, u); err != nil { t.Fatal(err) } o := &influxdb.Organization{Name: t.Name() + "-org"} - if err := service.CreateOrganization(ctx, o); err != nil { + if err := tenantSvc.CreateOrganization(ctx, o); err != nil { t.Fatal(err) } - if err := service.CreateUserResourceMapping(ctx, &influxdb.UserResourceMapping{ + if err := tenantSvc.CreateUserResourceMapping(ctx, &influxdb.UserResourceMapping{ ResourceType: influxdb.OrgsResourceType, ResourceID: o.ID, UserID: u.ID, @@ -296,7 +310,7 @@ func TestTaskRunCancellation(t *testing.T) { UserID: u.ID, Permissions: influxdb.OperPermissions(), } - if err := service.CreateAuthorization(context.Background(), &authz); err != nil { + if err := authSvc.CreateAuthorization(context.Background(), &authz); err != nil { t.Fatal(err) } diff --git a/kv/tenant_test.go b/kv/tenant_test.go deleted file mode 100644 index ee390ea786..0000000000 --- a/kv/tenant_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package kv_test - -import ( - "context" - "fmt" - "testing" - - "github.com/influxdata/influxdb/v2" - icontext "github.com/influxdata/influxdb/v2/context" - "github.com/influxdata/influxdb/v2/kv" - itesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltTenantService(t *testing.T) { - t.Skip("some tests fail using `*kv.Service` as `influxdb.TenantService`, see the NOTEs in `testing/tenant.go`.") - itesting.TenantService(t, initBoltTenantService) -} - -//lint:ignore U1000 erroneously flagged by staticcheck since it is used in skipped tests -func initBoltTenantService(t *testing.T, f itesting.TenantFields) (influxdb.TenantService, func()) { - s, closeStore, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new bolt kv store: %v", err) - } - - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - - // Create a mapping from user-specified IDs to kv generated ones. - // This way we can map the user intent to the actual IDs. - uIDs := make(map[influxdb.ID]influxdb.ID, len(f.Users)) - oIDs := make(map[influxdb.ID]influxdb.ID, len(f.Organizations)) - bIDs := make(map[influxdb.ID]influxdb.ID, len(f.Buckets)) - - for _, u := range f.Users { - id := u.ID - if err := svc.CreateUser(ctx, u); err != nil { - t.Fatalf("error populating users: %v", err) - } - uIDs[id] = u.ID - } - for i := range f.Passwords { - if err := svc.SetPassword(ctx, f.Users[i].ID, f.Passwords[i]); err != nil { - t.Fatalf("error setting passsword user, %s %s: %v", f.Users[i].Name, f.Passwords[i], err) - } - } - - // Preserve only the urms that do not express ownership of an org. - // Those will be expressed on org creation through the authorizer. - urms := f.UserResourceMappings[:0] - orgOwners := make(map[influxdb.ID]influxdb.ID) - for _, urm := range f.UserResourceMappings { - if urm.ResourceType == influxdb.OrgsResourceType && urm.UserType == influxdb.Owner { - orgOwners[urm.ResourceID] = urm.UserID - } else { - urms = append(urms, urm) - } - } - for _, o := range f.Organizations { - id := o.ID - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, &influxdb.Authorization{ - UserID: uIDs[orgOwners[id]], - }) - if err := svc.CreateOrganization(ctx, o); err != nil { - t.Fatalf("error populating orgs: %v", err) - } - oIDs[id] = o.ID - } - for _, b := range f.Buckets { - id := b.ID - b.OrgID = oIDs[b.OrgID] - if err := svc.CreateBucket(ctx, b); err != nil { - t.Fatalf("error populating buckets: %v", err) - } - bIDs[id] = b.ID - } - for _, urm := range urms { - urm.UserID = uIDs[urm.UserID] - switch urm.ResourceType { - case influxdb.BucketsResourceType: - urm.ResourceID = bIDs[urm.ResourceID] - case influxdb.OrgsResourceType: - urm.ResourceID = oIDs[urm.ResourceID] - default: - panic(fmt.Errorf("invalid resource type: %v", urm.ResourceType)) - } - if err := svc.CreateUserResourceMapping(ctx, urm); err != nil { - t.Fatalf("error populating urms: %v", err) - } - } - - return svc, func() { - closeStore() - } -} diff --git a/kv/unique.go b/kv/unique.go deleted file mode 100644 index 299b3e4c29..0000000000 --- a/kv/unique.go +++ /dev/null @@ -1,98 +0,0 @@ -package kv - -import ( - "context" - "fmt" - - influxdb "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kit/tracing" -) - -// UnexpectedIndexError is used when the error comes from an internal system. -func UnexpectedIndexError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("unexpected error retrieving index; Err: %v", err), - Op: "kv/index", - } -} - -// NotUniqueError is used when attempting to create a resource that already -// exists. -var NotUniqueError = &influxdb.Error{ - Code: influxdb.EConflict, - Msg: "name already exists", -} - -// NotUniqueIDError is used when attempting to create an org or bucket that already -// exists. -var NotUniqueIDError = &influxdb.Error{ - Code: influxdb.EConflict, - Msg: "ID already exists", -} - -func (s *Service) unique(ctx context.Context, tx Tx, indexBucket, indexKey []byte) error { - bucket, err := tx.Bucket(indexBucket) - if err != nil { - return UnexpectedIndexError(err) - } - - _, err = bucket.Get(indexKey) - // if not found then this is _unique_. - if IsNotFound(err) { - return nil - } - - // no error means this is not unique - if err == nil { - return NotUniqueError - } - - // any other error is some sort of internal server error - return UnexpectedIndexError(err) -} - -func (s *Service) uniqueID(ctx context.Context, tx Tx, bucket []byte, id influxdb.ID) error { - span, _ := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - encodedID, err := id.Encode() - if err != nil { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Err: err, - } - } - - b, err := tx.Bucket(bucket) - if err != nil { - return err - } - - _, err = b.Get(encodedID) - if IsNotFound(err) { - return nil - } - - return NotUniqueIDError -} - -// generateSafeID attempts to create ids for buckets -// and orgs that are without backslash, commas, and spaces, BUT ALSO do not already exist. -func (s *Service) generateSafeID(ctx context.Context, tx Tx, bucket []byte, gen influxdb.IDGenerator) (influxdb.ID, error) { - for i := 0; i < MaxIDGenerationN; i++ { - id := gen.ID() - - err := s.uniqueID(ctx, tx, bucket, id) - if err == nil { - return id, nil - } - - if err == NotUniqueIDError { - continue - } - - return influxdb.InvalidID(), err - } - return influxdb.InvalidID(), ErrFailureGeneratingID -} diff --git a/kv/urm.go b/kv/urm.go deleted file mode 100644 index d8e81f8aa4..0000000000 --- a/kv/urm.go +++ /dev/null @@ -1,497 +0,0 @@ -package kv - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/influxdata/influxdb/v2" - icontext "github.com/influxdata/influxdb/v2/context" - "github.com/influxdata/influxdb/v2/kit/tracing" - "go.uber.org/zap" -) - -var ( - urmBucket = []byte("userresourcemappingsv1") - - // ErrInvalidURMID is used when the service was provided - // an invalid ID format. - ErrInvalidURMID = &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "provided user resource mapping ID has invalid format", - } - - // ErrURMNotFound is used when the user resource mapping is not found. - ErrURMNotFound = &influxdb.Error{ - Msg: "user to resource mapping not found", - Code: influxdb.ENotFound, - } - - // URMByUserIndeMappingx is the mapping description of an index - // between a user and a URM - URMByUserIndexMapping = NewIndexMapping( - urmBucket, - []byte("userresourcemappingsbyuserindexv1"), - func(v []byte) ([]byte, error) { - var urm influxdb.UserResourceMapping - if err := json.Unmarshal(v, &urm); err != nil { - return nil, err - } - - id, _ := urm.UserID.Encode() - return id, nil - }, - ) -) - -// UnavailableURMServiceError is used if we aren't able to interact with the -// store, it means the store is not available at the moment (e.g. network). -func UnavailableURMServiceError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("Unable to connect to resource mapping service. Please try again; Err: %v", err), - Op: "kv/userResourceMapping", - } -} - -// CorruptURMError is used when the config cannot be unmarshalled from the -// bytes stored in the kv. -func CorruptURMError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("Unknown internal user resource mapping data error; Err: %v", err), - Op: "kv/userResourceMapping", - } -} - -// ErrUnprocessableMapping is used when a user resource mapping is not able to be converted to JSON. -func ErrUnprocessableMapping(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EUnprocessableEntity, - Msg: fmt.Sprintf("unable to convert mapping of user to resource into JSON; Err %v", err), - } -} - -// NonUniqueMappingError is an internal error when a user already has -// been mapped to a resource -func NonUniqueMappingError(userID influxdb.ID) error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("Unexpected error when assigning user to a resource: mapping for user %s already exists", userID.String()), - } -} - -func filterMappingsFn(filter influxdb.UserResourceMappingFilter) func(m *influxdb.UserResourceMapping) bool { - return func(mapping *influxdb.UserResourceMapping) bool { - return (!filter.UserID.Valid() || (filter.UserID == mapping.UserID)) && - (!filter.ResourceID.Valid() || (filter.ResourceID == mapping.ResourceID)) && - (filter.UserType == "" || (filter.UserType == mapping.UserType)) && - (filter.ResourceType == "" || (filter.ResourceType == mapping.ResourceType)) - } -} - -// FindUserResourceMappings returns a list of UserResourceMappings that match filter and the total count of matching mappings. -func (s *Service) FindUserResourceMappings(ctx context.Context, filter influxdb.UserResourceMappingFilter, opt ...influxdb.FindOptions) ([]*influxdb.UserResourceMapping, int, error) { - var ms []*influxdb.UserResourceMapping - err := s.kv.View(ctx, func(tx Tx) error { - var err error - ms, err = s.findUserResourceMappings(ctx, tx, filter) - return err - }) - - if err != nil { - return nil, 0, err - } - - return ms, len(ms), nil -} - -func userResourceMappingPredicate(filter influxdb.UserResourceMappingFilter) CursorPredicateFunc { - switch { - case filter.ResourceID.Valid() && filter.UserID.Valid(): - keyPredicate := filter.ResourceID.String() + filter.UserID.String() - return func(key, _ []byte) bool { - return len(key) >= 32 && string(key[:32]) == keyPredicate - } - - case !filter.ResourceID.Valid() && filter.UserID.Valid(): - keyPredicate := filter.UserID.String() - return func(key, _ []byte) bool { - return len(key) >= 32 && string(key[16:32]) == keyPredicate - } - - case filter.ResourceID.Valid() && !filter.UserID.Valid(): - keyPredicate := filter.ResourceID.String() - return func(key, _ []byte) bool { - return len(key) >= 16 && string(key[:16]) == keyPredicate - } - - default: - return nil - } -} - -func (s *Service) findUserResourceMappings(ctx context.Context, tx Tx, filter influxdb.UserResourceMappingFilter) (ms []*influxdb.UserResourceMapping, _ error) { - filterFn := filterMappingsFn(filter) - if filter.UserID.Valid() { - // urm by user index lookup - userID, _ := filter.UserID.Encode() - if err := s.urmByUserIndex.Walk(ctx, tx, userID, func(k, v []byte) (bool, error) { - m := &influxdb.UserResourceMapping{} - if err := json.Unmarshal(v, m); err != nil { - return false, CorruptURMError(err) - } - - if filterFn(m) { - ms = append(ms, m) - } - - return true, nil - }); err != nil { - return nil, err - } - - if len(ms) > 0 { - return - } - } - - pred := userResourceMappingPredicate(filter) - err := s.forEachUserResourceMapping(ctx, tx, pred, func(m *influxdb.UserResourceMapping) bool { - if filterFn(m) { - ms = append(ms, m) - } - return true - }) - - return ms, err -} - -func (s *Service) findUserResourceMapping(ctx context.Context, tx Tx, filter influxdb.UserResourceMappingFilter) (*influxdb.UserResourceMapping, error) { - ms, err := s.findUserResourceMappings(ctx, tx, filter) - if err != nil { - return nil, err - } - - if len(ms) == 0 { - return nil, ErrURMNotFound - } - - return ms[0], nil -} - -// CreateUserResourceMapping associates a user to a resource either as a member -// or owner. -func (s *Service) CreateUserResourceMapping(ctx context.Context, m *influxdb.UserResourceMapping) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.createUserResourceMapping(ctx, tx, m) - }) -} - -// CreateUserResourceMappingTx is used when importing kv as a library -func (s *Service) CreateUserResourceMappingTx(ctx context.Context, tx Tx, m *influxdb.UserResourceMapping) error { - return s.createUserResourceMapping(ctx, tx, m) -} - -func (s *Service) createUserResourceMapping(ctx context.Context, tx Tx, m *influxdb.UserResourceMapping) error { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - if err := s.uniqueUserResourceMapping(ctx, tx, m); err != nil { - return err - } - - v, err := json.Marshal(m) - if err != nil { - return ErrUnprocessableMapping(err) - } - - key, err := userResourceKey(m) - if err != nil { - return err - } - - b, err := tx.Bucket(urmBucket) - if err != nil { - return UnavailableURMServiceError(err) - } - - if err := b.Put(key, v); err != nil { - return UnavailableURMServiceError(err) - } - - userID, err := m.UserID.Encode() - if err != nil { - return err - } - - // insert urm into by user index - if err := s.urmByUserIndex.Insert(tx, userID, key); err != nil { - return err - } - - if m.ResourceType == influxdb.OrgsResourceType { - return s.createOrgDependentMappings(ctx, tx, m) - } - - return nil -} - -// This method creates the user/resource mappings for resources that belong to an organization. -func (s *Service) createOrgDependentMappings(ctx context.Context, tx Tx, m *influxdb.UserResourceMapping) error { - span, ctx := tracing.StartSpanFromContext(ctx) - defer span.Finish() - - bf := influxdb.BucketFilter{OrganizationID: &m.ResourceID} - bs, err := s.findBuckets(ctx, tx, bf) - if err != nil { - return err - } - for _, b := range bs { - m := &influxdb.UserResourceMapping{ - ResourceType: influxdb.BucketsResourceType, - ResourceID: b.ID, - UserType: m.UserType, - UserID: m.UserID, - } - if err := s.createUserResourceMapping(ctx, tx, m); err != nil { - return err - } - // TODO(desa): add support for all other resource types. - } - - return nil -} - -func userResourceKey(m *influxdb.UserResourceMapping) ([]byte, error) { - encodedResourceID, err := m.ResourceID.Encode() - if err != nil { - return nil, ErrInvalidURMID - } - - encodedUserID, err := m.UserID.Encode() - if err != nil { - return nil, ErrInvalidURMID - } - - key := make([]byte, len(encodedResourceID)+len(encodedUserID)) - copy(key, encodedResourceID) - copy(key[len(encodedResourceID):], encodedUserID) - - return key, nil -} - -func (s *Service) forEachUserResourceMapping(ctx context.Context, tx Tx, pred CursorPredicateFunc, fn func(*influxdb.UserResourceMapping) bool) error { - b, err := tx.Bucket(urmBucket) - if err != nil { - return UnavailableURMServiceError(err) - } - var cur Cursor - if pred != nil { - cur, err = b.Cursor(WithCursorHintPredicate(pred)) - } else { - cur, err = b.Cursor() - } - if err != nil { - return UnavailableURMServiceError(err) - } - - for k, v := cur.First(); k != nil; k, v = cur.Next() { - m := &influxdb.UserResourceMapping{} - if err := json.Unmarshal(v, m); err != nil { - return CorruptURMError(err) - } - - if !fn(m) { - break - } - } - - return nil -} - -func (s *Service) uniqueUserResourceMapping(ctx context.Context, tx Tx, m *influxdb.UserResourceMapping) error { - key, err := userResourceKey(m) - if err != nil { - return err - } - - b, err := tx.Bucket(urmBucket) - if err != nil { - return UnavailableURMServiceError(err) - } - - _, err = b.Get(key) - if !IsNotFound(err) { - return NonUniqueMappingError(m.UserID) - } - - return nil -} - -// DeleteUserResourceMapping deletes a user resource mapping. -func (s *Service) DeleteUserResourceMapping(ctx context.Context, resourceID influxdb.ID, userID influxdb.ID) error { - return s.kv.Update(ctx, func(tx Tx) error { - // TODO(goller): I don't think this find is needed as delete also finds. - m, err := s.findUserResourceMapping(ctx, tx, influxdb.UserResourceMappingFilter{ - ResourceID: resourceID, - UserID: userID, - }) - if err != nil { - return err - } - - filter := influxdb.UserResourceMappingFilter{ - ResourceID: resourceID, - UserID: userID, - } - if err := s.deleteUserResourceMapping(ctx, tx, filter); err != nil { - return err - } - - if m.ResourceType == influxdb.OrgsResourceType { - return s.deleteOrgDependentMappings(ctx, tx, m) - } - - return nil - }) -} - -func (s *Service) deleteUserResourceMapping(ctx context.Context, tx Tx, filter influxdb.UserResourceMappingFilter) error { - // TODO(goller): do we really need to find here? Seems like a Get is - // good enough. - ms, err := s.findUserResourceMappings(ctx, tx, filter) - if err != nil { - return err - } - if len(ms) == 0 { - return ErrURMNotFound - } - - key, err := userResourceKey(ms[0]) - if err != nil { - return err - } - - b, err := tx.Bucket(urmBucket) - if err != nil { - return UnavailableURMServiceError(err) - } - - _, err = b.Get(key) - if IsNotFound(err) { - return ErrURMNotFound - } - if err != nil { - return UnavailableURMServiceError(err) - } - - if err := b.Delete(key); err != nil { - return UnavailableURMServiceError(err) - } - - userID, err := ms[0].UserID.Encode() - if err != nil { - return err - } - - // remove user resource mapping from by user index - if err := s.urmByUserIndex.Delete(tx, userID, key); err != nil { - return err - } - - return nil -} - -func (s *Service) deleteUserResourceMappings(ctx context.Context, tx Tx, filter influxdb.UserResourceMappingFilter) error { - ms, err := s.findUserResourceMappings(ctx, tx, filter) - if err != nil { - return err - } - for _, m := range ms { - key, err := userResourceKey(m) - if err != nil { - return err - } - - b, err := tx.Bucket(urmBucket) - if err != nil { - return UnavailableURMServiceError(err) - } - - _, err = b.Get(key) - if IsNotFound(err) { - return ErrURMNotFound - } - if err != nil { - return UnavailableURMServiceError(err) - } - - if err := b.Delete(key); err != nil { - return UnavailableURMServiceError(err) - } - - userID, err := m.UserID.Encode() - if err != nil { - return err - } - - // remove user resource mapping from by user index - if err := s.urmByUserIndex.Delete(tx, userID, key); err != nil { - return err - } - } - return nil -} - -// This method deletes the user/resource mappings for resources that belong to an organization. -func (s *Service) deleteOrgDependentMappings(ctx context.Context, tx Tx, m *influxdb.UserResourceMapping) error { - bf := influxdb.BucketFilter{OrganizationID: &m.ResourceID} - bs, err := s.findBuckets(ctx, tx, bf) - if err != nil { - return err - } - for _, b := range bs { - if err := s.deleteUserResourceMapping(ctx, tx, influxdb.UserResourceMappingFilter{ - ResourceType: influxdb.BucketsResourceType, - ResourceID: b.ID, - UserID: m.UserID, - }); err != nil { - if influxdb.ErrorCode(err) == influxdb.ENotFound { - s.log.Info("URM bucket is missing", zap.Stringer("orgID", m.ResourceID)) - continue - } - return err - } - // TODO(desa): add support for all other resource types. - } - - return nil -} - -func (s *Service) addResourceOwner(ctx context.Context, tx Tx, rt influxdb.ResourceType, id influxdb.ID) error { - a, err := icontext.GetAuthorizer(ctx) - if err != nil { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("could not find authorizer on context when adding user to resource type %s", rt), - } - } - - urm := &influxdb.UserResourceMapping{ - ResourceID: id, - ResourceType: rt, - UserID: a.GetUserID(), - UserType: influxdb.Owner, - } - - if err := s.createUserResourceMapping(ctx, tx, urm); err != nil { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: "could not create user resource mapping", - Err: err, - } - } - - return nil -} diff --git a/kv/urm_private_test.go b/kv/urm_private_test.go deleted file mode 100644 index 06d377ad67..0000000000 --- a/kv/urm_private_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package kv - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2" -) - -func Test_userResourceMappingPredicate(t *testing.T) { - mk := func(rid, uid influxdb.ID) (urm *influxdb.UserResourceMapping, key []byte) { - t.Helper() - urm = &influxdb.UserResourceMapping{UserID: rid, ResourceID: uid} - key, err := userResourceKey(urm) - if err != nil { - t.Fatal(err) - } - return urm, key - } - - t.Run("match only ResourceID", func(t *testing.T) { - u, k := mk(10, 20) - f := influxdb.UserResourceMappingFilter{ResourceID: u.ResourceID} - fn := userResourceMappingPredicate(f) - if got, exp := fn(k, nil), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - - _, k = mk(10, 21) - if got, exp := fn(k, nil), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("match only UserID", func(t *testing.T) { - u, k := mk(10, 20) - f := influxdb.UserResourceMappingFilter{UserID: u.UserID} - fn := userResourceMappingPredicate(f) - if got, exp := fn(k, nil), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - - _, k = mk(11, 20) - if got, exp := fn(k, nil), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("match ResourceID and UserID", func(t *testing.T) { - u, k := mk(10, 20) - f := influxdb.UserResourceMappingFilter{ResourceID: u.ResourceID, UserID: u.UserID} - fn := userResourceMappingPredicate(f) - if got, exp := fn(k, nil), true; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - - _, k = mk(11, 20) - if got, exp := fn(k, nil), false; got != exp { - t.Errorf("unexpected result -got/+exp\n%s", cmp.Diff(got, exp)) - } - }) - - t.Run("no match function", func(t *testing.T) { - f := influxdb.UserResourceMappingFilter{} - fn := userResourceMappingPredicate(f) - if fn != nil { - t.Errorf("expected nil") - } - }) -} diff --git a/kv/urm_test.go b/kv/urm_test.go deleted file mode 100644 index 99d2ca4ac9..0000000000 --- a/kv/urm_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/inmem" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/snowflake" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -type testable interface { - Helper() - Logf(string, ...interface{}) - Error(args ...interface{}) - Errorf(string, ...interface{}) - Fail() - Failed() bool - Name() string - FailNow() - Fatal(args ...interface{}) - Fatalf(format string, args ...interface{}) -} - -func TestBoltUserResourceMappingService(t *testing.T) { - influxdbtesting.UserResourceMappingService(initURMServiceFunc(NewTestBoltStore), t) -} - -func TestInmemUserResourceMappingService(t *testing.T) { - influxdbtesting.UserResourceMappingService(initURMServiceFunc(NewTestInmemStore), t) -} - -type userResourceMappingTestFunc func(influxdbtesting.UserResourceFields, *testing.T) (influxdb.UserResourceMappingService, func()) - -func initURMServiceFunc(storeFn func(*testing.T) (kv.SchemaStore, func(), error), confs ...kv.ServiceConfig) userResourceMappingTestFunc { - return func(f influxdbtesting.UserResourceFields, t *testing.T) (influxdb.UserResourceMappingService, func()) { - s, closeStore, err := storeFn(t) - if err != nil { - t.Fatalf("failed to create new kv store: %v", err) - } - - svc, closeSvc := initUserResourceMappingService(s, f, t, confs...) - return svc, func() { - closeSvc() - closeStore() - } - } -} - -func initUserResourceMappingService(s kv.SchemaStore, f influxdbtesting.UserResourceFields, t testable, configs ...kv.ServiceConfig) (influxdb.UserResourceMappingService, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s, configs...) - - for _, o := range f.Organizations { - if err := svc.CreateOrganization(ctx, o); err != nil { - t.Fatalf("failed to create org %q", err) - } - } - - for _, u := range f.Users { - if err := svc.CreateUser(ctx, u); err != nil { - t.Fatalf("failed to create user %q", err) - } - } - - for _, b := range f.Buckets { - b.ID = svc.BucketIDs.ID() - if err := svc.PutBucket(ctx, b); err != nil { - t.Fatalf("failed to create bucket %q", err) - } - } - - for _, m := range f.UserResourceMappings { - if err := svc.CreateUserResourceMapping(ctx, m); err != nil { - t.Fatalf("failed to populate mappings %q", err) - } - } - - return svc, func() { - for _, m := range f.UserResourceMappings { - if err := svc.DeleteUserResourceMapping(ctx, m.ResourceID, m.UserID); err != nil { - t.Logf("failed to remove user resource mapping: %v", err) - } - } - - for _, b := range f.Buckets { - if err := svc.DeleteBucket(ctx, b.ID); err != nil { - t.Logf("failed to delete org", err) - } - } - - for _, u := range f.Users { - if err := svc.DeleteUser(ctx, u.ID); err != nil { - t.Fatalf("failed to delete user %q", err) - } - } - - for _, o := range f.Organizations { - if err := svc.DeleteOrganization(ctx, o.ID); err != nil { - t.Logf("failed to delete org", err) - } - } - } -} - -func BenchmarkReadURMs(b *testing.B) { - urms := influxdbtesting.UserResourceFields{ - UserResourceMappings: make([]*influxdb.UserResourceMapping, 10000), - } - idgen := snowflake.NewDefaultIDGenerator() - users := make([]influxdb.ID, 10) - for i := 0; i < 10; i++ { - users[i] = idgen.ID() - } - - for i := 0; i < 10000; i++ { - urms.UserResourceMappings[i] = &influxdb.UserResourceMapping{ - ResourceID: idgen.ID(), - UserID: users[i%len(users)], - UserType: influxdb.Member, - ResourceType: influxdb.BucketsResourceType, - } - } - st := inmem.NewKVStore() - initUserResourceMappingService(st, urms, b) - svc := kv.NewService(zaptest.NewLogger(b), st) - b.ResetTimer() - for i := 0; i < b.N; i++ { - svc.FindUserResourceMappings(context.Background(), influxdb.UserResourceMappingFilter{ - UserID: users[0], - }) - } -} diff --git a/kv/user.go b/kv/user.go deleted file mode 100644 index 5f810a50d9..0000000000 --- a/kv/user.go +++ /dev/null @@ -1,622 +0,0 @@ -package kv - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/influxdata/influxdb/v2" - icontext "github.com/influxdata/influxdb/v2/context" -) - -var ( - userBucket = []byte("usersv1") - userIndex = []byte("userindexv1") -) - -var _ influxdb.UserService = (*Service)(nil) -var _ influxdb.UserOperationLogService = (*Service)(nil) - -func (s *Service) userBucket(tx Tx) (Bucket, error) { - b, err := tx.Bucket([]byte(userBucket)) - if err != nil { - return nil, UnexpectedUserBucketError(err) - } - - return b, nil -} - -func (s *Service) userIndexBucket(tx Tx) (Bucket, error) { - b, err := tx.Bucket([]byte(userIndex)) - if err != nil { - return nil, UnexpectedUserIndexError(err) - } - - return b, nil -} - -// FindUserByID retrieves a user by id. -func (s *Service) FindUserByID(ctx context.Context, id influxdb.ID) (*influxdb.User, error) { - var u *influxdb.User - - err := s.kv.View(ctx, func(tx Tx) error { - usr, err := s.findUserByID(ctx, tx, id) - if err != nil { - return err - } - u = usr - return nil - }) - - if err != nil { - return nil, err - } - - return u, nil -} - -func (s *Service) findUserByID(ctx context.Context, tx Tx, id influxdb.ID) (*influxdb.User, error) { - encodedID, err := id.Encode() - if err != nil { - return nil, InvalidUserIDError(err) - } - - b, err := s.userBucket(tx) - if err != nil { - return nil, err - } - - v, err := b.Get(encodedID) - if IsNotFound(err) { - return nil, ErrUserNotFound - } - - if err != nil { - return nil, ErrInternalUserServiceError(err) - } - - return UnmarshalUser(v) -} - -// UnmarshalUser turns the stored byte slice in the kv into a *influxdb.User. -func UnmarshalUser(v []byte) (*influxdb.User, error) { - u := &influxdb.User{} - if err := json.Unmarshal(v, u); err != nil { - return nil, ErrCorruptUser(err) - } - - return u, nil -} - -// MarshalUser turns an *influxdb.User into a byte slice. -func MarshalUser(u *influxdb.User) ([]byte, error) { - v, err := json.Marshal(u) - if err != nil { - return nil, ErrUnprocessableUser(err) - } - - return v, nil -} - -// FindUserByName returns a user by name for a particular user. -func (s *Service) FindUserByName(ctx context.Context, n string) (*influxdb.User, error) { - var u *influxdb.User - - err := s.kv.View(ctx, func(tx Tx) error { - usr, err := s.findUserByName(ctx, tx, n) - if err != nil { - return err - } - u = usr - return nil - }) - - return u, err -} - -func (s *Service) findUserByName(ctx context.Context, tx Tx, n string) (*influxdb.User, error) { - b, err := s.userIndexBucket(tx) - if err != nil { - return nil, err - } - - uid, err := b.Get(userIndexKey(n)) - if err == ErrKeyNotFound { - return nil, ErrUserNotFound - } - if err != nil { - return nil, ErrInternalUserServiceError(err) - } - - var id influxdb.ID - if err := id.Decode(uid); err != nil { - return nil, ErrCorruptUserID(err) - } - return s.findUserByID(ctx, tx, id) -} - -// FindUser retrieves a user using an arbitrary user filter. -// Filters using ID, or Name should be efficient. -// Other filters will do a linear scan across users until it finds a match. -func (s *Service) FindUser(ctx context.Context, filter influxdb.UserFilter) (*influxdb.User, error) { - if filter.ID != nil { - u, err := s.FindUserByID(ctx, *filter.ID) - if err != nil { - return nil, err - } - return u, nil - } - - if filter.Name != nil { - return s.FindUserByName(ctx, *filter.Name) - } - - return nil, ErrUserNotFound -} - -func filterUsersFn(filter influxdb.UserFilter) func(u *influxdb.User) bool { - if filter.ID != nil { - return func(u *influxdb.User) bool { - return u.ID.Valid() && u.ID == *filter.ID - } - } - - if filter.Name != nil { - return func(u *influxdb.User) bool { - return u.Name == *filter.Name - } - } - - return func(u *influxdb.User) bool { return true } -} - -// FindUsers retrieves all users that match an arbitrary user filter. -// Filters using ID, or Name should be efficient. -// Other filters will do a linear scan across all users searching for a match. -func (s *Service) FindUsers(ctx context.Context, filter influxdb.UserFilter, opt ...influxdb.FindOptions) ([]*influxdb.User, int, error) { - if filter.ID != nil { - u, err := s.FindUserByID(ctx, *filter.ID) - if err != nil { - return nil, 0, err - } - - return []*influxdb.User{u}, 1, nil - } - - if filter.Name != nil { - u, err := s.FindUserByName(ctx, *filter.Name) - if err != nil { - return nil, 0, err - } - - return []*influxdb.User{u}, 1, nil - } - - us := []*influxdb.User{} - filterFn := filterUsersFn(filter) - err := s.kv.View(ctx, func(tx Tx) error { - return s.forEachUser(ctx, tx, func(u *influxdb.User) bool { - if filterFn(u) { - us = append(us, u) - } - return true - }) - }) - - if err != nil { - return nil, 0, err - } - - return us, len(us), nil -} - -// CreateUser creates a influxdb user and sets b.ID. -func (s *Service) CreateUser(ctx context.Context, u *influxdb.User) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.createUser(ctx, tx, u) - }) -} - -// CreateUserTx is used when importing kv as a library -func (s *Service) CreateUserTx(ctx context.Context, tx Tx, u *influxdb.User) error { - return s.createUser(ctx, tx, u) -} - -func (s *Service) createUser(ctx context.Context, tx Tx, u *influxdb.User) error { - if err := s.uniqueUserName(ctx, tx, u); err != nil { - return err - } - - u.ID = s.IDGenerator.ID() - u.Status = influxdb.Active - if err := s.appendUserEventToLog(ctx, tx, u.ID, userCreatedEvent); err != nil { - return err - } - - return s.putUser(ctx, tx, u) -} - -// PutUser will put a user without setting an ID. -func (s *Service) PutUser(ctx context.Context, u *influxdb.User) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.putUser(ctx, tx, u) - }) -} - -func (s *Service) putUser(ctx context.Context, tx Tx, u *influxdb.User) error { - v, err := MarshalUser(u) - if err != nil { - return err - } - encodedID, err := u.ID.Encode() - if err != nil { - return InvalidUserIDError(err) - } - - idx, err := s.userIndexBucket(tx) - if err != nil { - return err - } - - if err := idx.Put(userIndexKey(u.Name), encodedID); err != nil { - return ErrInternalUserServiceError(err) - } - - b, err := s.userBucket(tx) - if err != nil { - return err - } - - if err := b.Put(encodedID, v); err != nil { - return ErrInternalUserServiceError(err) - } - - return nil -} - -func userIndexKey(n string) []byte { - return []byte(n) -} - -// forEachUser will iterate through all users while fn returns true. -func (s *Service) forEachUser(ctx context.Context, tx Tx, fn func(*influxdb.User) bool) error { - b, err := s.userBucket(tx) - if err != nil { - return err - } - - cur, err := b.ForwardCursor(nil) - if err != nil { - return ErrInternalUserServiceError(err) - } - - for k, v := cur.Next(); k != nil; k, v = cur.Next() { - u, err := UnmarshalUser(v) - if err != nil { - return err - } - if !fn(u) { - break - } - } - - return nil -} - -func (s *Service) uniqueUserName(ctx context.Context, tx Tx, u *influxdb.User) error { - key := userIndexKey(u.Name) - - // if the name is not unique across all users in all organizations, then, - // do not allow creation. - err := s.unique(ctx, tx, userIndex, key) - if err == NotUniqueError { - return UserAlreadyExistsError(u.Name) - } - return err -} - -// UpdateUser updates a user according the parameters set on upd. -func (s *Service) UpdateUser(ctx context.Context, id influxdb.ID, upd influxdb.UserUpdate) (*influxdb.User, error) { - var u *influxdb.User - err := s.kv.Update(ctx, func(tx Tx) error { - usr, err := s.updateUser(ctx, tx, id, upd) - if err != nil { - return err - } - u = usr - return nil - }) - - if err != nil { - return nil, err - } - - return u, nil -} - -func (s *Service) updateUser(ctx context.Context, tx Tx, id influxdb.ID, upd influxdb.UserUpdate) (*influxdb.User, error) { - u, err := s.findUserByID(ctx, tx, id) - if err != nil { - return nil, err - } - - if upd.Name != nil { - if err := s.removeUserFromIndex(ctx, tx, id, u.Name); err != nil { - return nil, err - } - - u.Name = *upd.Name - } - - if upd.Status != nil { - if *upd.Status != u.Status && *upd.Status == "inactive" { - // Disable tasks - tasks, _, err := s.findTasksByUser(ctx, tx, influxdb.TaskFilter{User: &id}) - if err != nil { - return nil, err - } - status := influxdb.TaskStatusInactive - - for _, task := range tasks { - _, err := s.UpdateTask(ctx, task.ID, influxdb.TaskUpdate{Status: &status}) - if err != nil { - return nil, err - } - } - } - u.Status = *upd.Status - } - - if err := s.appendUserEventToLog(ctx, tx, u.ID, userUpdatedEvent); err != nil { - return nil, err - } - - if err := s.putUser(ctx, tx, u); err != nil { - return nil, err - } - - return u, nil -} - -func (s *Service) removeUserFromIndex(ctx context.Context, tx Tx, id influxdb.ID, name string) error { - // Users are indexed by name and so the user index must be pruned - // when name is modified. - idx, err := s.userIndexBucket(tx) - if err != nil { - return err - } - - if err := idx.Delete(userIndexKey(name)); err != nil { - return ErrInternalUserServiceError(err) - } - - return nil -} - -// DeleteUser deletes a user and prunes it from the index. -func (s *Service) DeleteUser(ctx context.Context, id influxdb.ID) error { - return s.kv.Update(ctx, func(tx Tx) error { - return s.deleteUser(ctx, tx, id) - }) -} - -func (s *Service) deleteUser(ctx context.Context, tx Tx, id influxdb.ID) error { - u, err := s.findUserByID(ctx, tx, id) - if err != nil { - return err - } - - if err := s.deleteUsersAuthorizations(ctx, tx, id); err != nil { - return err - } - - encodedID, err := id.Encode() - if err != nil { - return InvalidUserIDError(err) - } - - idx, err := s.userIndexBucket(tx) - if err != nil { - return err - } - - if err := idx.Delete(userIndexKey(u.Name)); err != nil { - return ErrInternalUserServiceError(err) - } - - b, err := s.userBucket(tx) - if err != nil { - return err - } - if err := b.Delete(encodedID); err != nil { - return ErrInternalUserServiceError(err) - } - - if err := s.deleteUserResourceMappings(ctx, tx, influxdb.UserResourceMappingFilter{ - UserID: id, - }); err != nil { - return err - } - - return nil -} - -func (s *Service) FindPermissionForUser(ctx context.Context, uid influxdb.ID) (influxdb.PermissionSet, error) { - return nil, &influxdb.Error{ - Code: influxdb.EInternal, - Msg: "not implemented", - } -} - -func (s *Service) deleteUsersAuthorizations(ctx context.Context, tx Tx, id influxdb.ID) error { - authFilter := influxdb.AuthorizationFilter{ - UserID: &id, - } - as, err := s.findAuthorizations(ctx, tx, authFilter) - if err != nil { - return err - } - for _, a := range as { - if err := s.deleteAuthorization(ctx, tx, a.ID); err != nil { - return err - } - } - return nil -} - -// GetUserOperationLog retrieves a user operation log. -func (s *Service) GetUserOperationLog(ctx context.Context, id influxdb.ID, opts influxdb.FindOptions) ([]*influxdb.OperationLogEntry, int, error) { - // TODO(desa): might be worthwhile to allocate a slice of size opts.Limit - log := []*influxdb.OperationLogEntry{} - - err := s.kv.View(ctx, func(tx Tx) error { - key, err := encodeUserOperationLogKey(id) - if err != nil { - return err - } - - return s.ForEachLogEntryTx(ctx, tx, key, opts, func(v []byte, t time.Time) error { - e := &influxdb.OperationLogEntry{} - if err := json.Unmarshal(v, e); err != nil { - return err - } - e.Time = t - - log = append(log, e) - - return nil - }) - }) - - if err != nil && err != ErrKeyValueLogBoundsNotFound { - return nil, 0, err - } - - return log, len(log), nil -} - -const userOperationLogKeyPrefix = "user" - -// TODO(desa): what do we want these to be? -const ( - userCreatedEvent = "User Created" - userUpdatedEvent = "User Updated" -) - -func encodeUserOperationLogKey(id influxdb.ID) ([]byte, error) { - buf, err := id.Encode() - if err != nil { - return nil, err - } - return append([]byte(userOperationLogKeyPrefix), buf...), nil -} - -func (s *Service) appendUserEventToLog(ctx context.Context, tx Tx, id influxdb.ID, st string) error { - e := &influxdb.OperationLogEntry{ - Description: st, - } - // TODO(desa): this is fragile and non explicit since it requires an authorizer to be on context. It should be - // replaced with a higher level transaction so that adding to the log can take place in the http handler - // where the userID will exist explicitly. - a, err := icontext.GetAuthorizer(ctx) - if err == nil { - // Add the user to the log if you can, but don't error if its not there. - e.UserID = a.GetUserID() - } - - v, err := json.Marshal(e) - if err != nil { - return err - } - - k, err := encodeUserOperationLogKey(id) - if err != nil { - return err - } - - return s.AddLogEntryTx(ctx, tx, k, v, s.Now()) -} - -var ( - // ErrUserNotFound is used when the user is not found. - ErrUserNotFound = &influxdb.Error{ - Msg: "user not found", - Code: influxdb.ENotFound, - } -) - -// ErrInternalUserServiceError is used when the error comes from an internal system. -func ErrInternalUserServiceError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Err: err, - } -} - -// UserAlreadyExistsError is used when attempting to create a user with a name -// that already exists. -func UserAlreadyExistsError(n string) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EConflict, - Msg: fmt.Sprintf("user with name %s already exists", n), - } -} - -// UnexpectedUserBucketError is used when the error comes from an internal system. -func UnexpectedUserBucketError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("unexpected error retrieving user bucket; Err: %v", err), - Op: "kv/userBucket", - } -} - -// UnexpectedUserIndexError is used when the error comes from an internal system. -func UnexpectedUserIndexError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: fmt.Sprintf("unexpected error retrieving user index; Err: %v", err), - Op: "kv/userIndex", - } -} - -// InvalidUserIDError is used when a service was provided an invalid ID. -// This is some sort of internal server error. -func InvalidUserIDError(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "user id provided is invalid", - Err: err, - } -} - -// ErrCorruptUserID the ID stored in the Store is corrupt. -func ErrCorruptUserID(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInvalid, - Msg: "corrupt ID provided", - Err: err, - } -} - -// ErrCorruptUser is used when the user cannot be unmarshalled from the bytes -// stored in the kv. -func ErrCorruptUser(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EInternal, - Msg: "user could not be unmarshalled", - Err: err, - Op: "kv/UnmarshalUser", - } -} - -// ErrUnprocessableUser is used when a user is not able to be processed. -func ErrUnprocessableUser(err error) *influxdb.Error { - return &influxdb.Error{ - Code: influxdb.EUnprocessableEntity, - Msg: "user could not be marshalled", - Err: err, - Op: "kv/MarshalUser", - } -} diff --git a/kv/user_test.go b/kv/user_test.go deleted file mode 100644 index 70614f781a..0000000000 --- a/kv/user_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package kv_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestBoltUserService(t *testing.T) { - influxdbtesting.UserService(initBoltUserService, t) -} - -func initBoltUserService(f influxdbtesting.UserFields, t *testing.T) (influxdb.UserService, string, func()) { - s, closeBolt, err := NewTestBoltStore(t) - if err != nil { - t.Fatalf("failed to create new kv store: %v", err) - } - - svc, op, closeSvc := initUserService(s, f, t) - return svc, op, func() { - closeSvc() - closeBolt() - } -} - -func initUserService(s kv.SchemaStore, f influxdbtesting.UserFields, t *testing.T) (influxdb.UserService, string, func()) { - ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.IDGenerator = f.IDGenerator - - for _, u := range f.Users { - if err := svc.PutUser(ctx, u); err != nil { - t.Fatalf("failed to populate users") - } - } - - return svc, kv.OpPrefix, func() { - for _, u := range f.Users { - if err := svc.DeleteUser(ctx, u.ID); err != nil { - t.Logf("failed to remove users: %v", err) - } - } - } -} diff --git a/kv/variable.go b/kv/variable.go index 70296bed68..19f6028fbf 100644 --- a/kv/variable.go +++ b/kv/variable.go @@ -108,14 +108,6 @@ func (s *Service) findVariables(ctx context.Context, tx Tx, filter influxdb.Vari return s.findOrganizationVariables(ctx, tx, *filter.OrganizationID) } - if filter.Organization != nil { - o, err := s.findOrganizationByName(ctx, tx, *filter.Organization) - if err != nil { - return nil, err - } - return s.findOrganizationVariables(ctx, tx, o.ID) - } - var o influxdb.FindOptions if len(opt) > 0 { o = opt[0] @@ -160,7 +152,17 @@ func filterVariablesFn(filter influxdb.VariableFilter) func([]byte, interface{}) // FindVariables returns all variables in the store func (s *Service) FindVariables(ctx context.Context, filter influxdb.VariableFilter, opt ...influxdb.FindOptions) ([]*influxdb.Variable, error) { - // todo(leodido) > handle find options + if filter.Organization != nil { + o, err := s.orgs.FindOrganization(ctx, influxdb.OrganizationFilter{ + Name: filter.Organization, + }) + if err != nil { + return nil, err + } + + filter.OrganizationID = &o.ID + } + res := []*influxdb.Variable{} err := s.kv.View(ctx, func(tx Tx) error { variables, err := s.findVariables(ctx, tx, filter, opt...) diff --git a/kv/variable_test.go b/kv/variable_test.go index 6c43b01778..22b24f801a 100644 --- a/kv/variable_test.go +++ b/kv/variable_test.go @@ -6,6 +6,7 @@ import ( "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kv" + "github.com/influxdata/influxdb/v2/mock" influxdbtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -29,7 +30,7 @@ func initBoltVariableService(f influxdbtesting.VariableFields, t *testing.T) (in func initVariableService(s kv.SchemaStore, f influxdbtesting.VariableFields, t *testing.T) (influxdb.VariableService, string, func()) { ctx := context.Background() - svc := kv.NewService(zaptest.NewLogger(t), s) + svc := kv.NewService(zaptest.NewLogger(t), s, &mock.OrganizationService{}) svc.IDGenerator = f.IDGenerator svc.TimeGenerator = f.TimeGenerator if svc.TimeGenerator == nil { diff --git a/label/controller.go b/label/controller.go deleted file mode 100644 index 1d1a6d1b36..0000000000 --- a/label/controller.go +++ /dev/null @@ -1,94 +0,0 @@ -package label - -import ( - "context" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kit/feature" -) - -var _ influxdb.LabelService = (*LabelController)(nil) - -// LabelController is a temporary system for switching the label backend between -// the old and refactored versions with a feature flag -type LabelController struct { - flagger feature.Flagger - oldLabelService influxdb.LabelService - newLabelService influxdb.LabelService -} - -func NewLabelController(flagger feature.Flagger, oldLabelService, newLabelService influxdb.LabelService) *LabelController { - return &LabelController{ - flagger: flagger, - oldLabelService: oldLabelService, - newLabelService: newLabelService, - } -} - -func (s *LabelController) useNew(ctx context.Context) bool { - return feature.NewLabelPackage().Enabled(ctx, s.flagger) -} - -func (s *LabelController) CreateLabel(ctx context.Context, l *influxdb.Label) error { - if s.useNew(ctx) { - return s.newLabelService.CreateLabel(ctx, l) - } - return s.oldLabelService.CreateLabel(ctx, l) - -} - -func (s *LabelController) FindLabelByID(ctx context.Context, id influxdb.ID) (*influxdb.Label, error) { - if s.useNew(ctx) { - return s.newLabelService.FindLabelByID(ctx, id) - } - return s.oldLabelService.FindLabelByID(ctx, id) - -} - -func (s *LabelController) FindLabels(ctx context.Context, filter influxdb.LabelFilter, opt ...influxdb.FindOptions) ([]*influxdb.Label, error) { - if s.useNew(ctx) { - return s.newLabelService.FindLabels(ctx, filter, opt...) - } - return s.oldLabelService.FindLabels(ctx, filter, opt...) - -} - -func (s *LabelController) FindResourceLabels(ctx context.Context, filter influxdb.LabelMappingFilter) ([]*influxdb.Label, error) { - if s.useNew(ctx) { - return s.newLabelService.FindResourceLabels(ctx, filter) - } - return s.oldLabelService.FindResourceLabels(ctx, filter) - -} - -func (s *LabelController) UpdateLabel(ctx context.Context, id influxdb.ID, upd influxdb.LabelUpdate) (*influxdb.Label, error) { - if s.useNew(ctx) { - return s.newLabelService.UpdateLabel(ctx, id, upd) - } - return s.oldLabelService.UpdateLabel(ctx, id, upd) - -} - -func (s *LabelController) DeleteLabel(ctx context.Context, id influxdb.ID) error { - if s.useNew(ctx) { - return s.newLabelService.DeleteLabel(ctx, id) - } - return s.oldLabelService.DeleteLabel(ctx, id) - -} - -func (s *LabelController) CreateLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { - if s.useNew(ctx) { - return s.newLabelService.CreateLabelMapping(ctx, m) - } - return s.oldLabelService.CreateLabelMapping(ctx, m) - -} - -func (s *LabelController) DeleteLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error { - if s.useNew(ctx) { - return s.newLabelService.DeleteLabelMapping(ctx, m) - } - return s.oldLabelService.DeleteLabelMapping(ctx, m) - -} diff --git a/label/controller_test.go b/label/controller_test.go deleted file mode 100644 index a5b2c394d8..0000000000 --- a/label/controller_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package label_test - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kit/feature" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/label" - influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" -) - -func TestLabelServiceController(t *testing.T) { - influxdbtesting.LabelService(initBoltLabelServiceController, t) -} - -func initBoltLabelServiceController(f influxdbtesting.LabelFields, t *testing.T) (influxdb.LabelService, string, func()) { - newSvc, s, closer := initBoltLabelService(f, t) - st, _, _ := NewTestBoltStore(t) - oldSvc, _, _ := initOldLabelService(st, f, t) - - flagger := feature.DefaultFlagger() - return label.NewLabelController(flagger, oldSvc, newSvc), s, closer -} - -func initOldLabelService(s kv.Store, f influxdbtesting.LabelFields, t *testing.T) (influxdb.LabelService, string, func()) { - svc := kv.NewService(zaptest.NewLogger(t), s) - svc.IDGenerator = f.IDGenerator - - ctx := context.Background() - for _, l := range f.Labels { - if err := svc.PutLabel(ctx, l); err != nil { - t.Fatalf("failed to populate labels: %v", err) - } - } - - for _, m := range f.Mappings { - if err := svc.PutLabelMapping(ctx, m); err != nil { - t.Fatalf("failed to populate label mappings: %v", err) - } - } - - return svc, kv.OpPrefix, func() { - for _, l := range f.Labels { - if err := svc.DeleteLabel(ctx, l.ID); err != nil { - t.Logf("failed to remove label: %v", err) - } - } - } -} diff --git a/label/http_client.go b/label/http_client.go index 1ca3a1ce5c..ad5b83af48 100644 --- a/label/http_client.go +++ b/label/http_client.go @@ -22,6 +22,10 @@ func resourceIDPath(resourceType influxdb.ResourceType, resourceID influxdb.ID, return path.Join("/api/v2/", string(resourceType), resourceID.String(), p) } +func resourceIDMappingPath(resourceType influxdb.ResourceType, resourceID influxdb.ID, p string, labelID influxdb.ID) string { + return path.Join("/api/v2/", string(resourceType), resourceID.String(), p, labelID.String()) +} + // CreateLabel creates a new label. func (s *LabelClientService) CreateLabel(ctx context.Context, l *influxdb.Label) error { var lr labelResponse @@ -130,6 +134,6 @@ func (s *LabelClientService) DeleteLabelMapping(ctx context.Context, m *influxdb } return s.Client. - Delete(resourceIDPath(m.ResourceType, m.ResourceID, "labels")). + Delete(resourceIDMappingPath(m.ResourceType, m.ResourceID, "labels", m.LabelID)). Do(ctx) } diff --git a/label/service_test.go b/label/service_test.go index c185fe7787..a9733aa9c8 100644 --- a/label/service_test.go +++ b/label/service_test.go @@ -12,6 +12,7 @@ import ( "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration/all" "github.com/influxdata/influxdb/v2/label" + "github.com/influxdata/influxdb/v2/mock" influxdbtesting "github.com/influxdata/influxdb/v2/testing" "go.uber.org/zap/zaptest" ) @@ -68,13 +69,19 @@ func initLabelService(s kv.Store, f influxdbtesting.LabelFields, t *testing.T) ( t.Fatalf("failed to create label store: %v", err) } + if f.IDGenerator != nil { + st.IDGenerator = f.IDGenerator + } + svc := label.NewService(st) ctx := context.Background() for _, l := range f.Labels { - if err := svc.CreateLabel(ctx, l); err != nil { - t.Fatalf("failed to populate labels: %v", err) - } + mock.SetIDForFunc(&st.IDGenerator, l.ID, func() { + if err := svc.CreateLabel(ctx, l); err != nil { + t.Fatalf("failed to populate labels: %v", err) + } + }) } for _, m := range f.Mappings { diff --git a/label/storage.go b/label/storage.go index d65aaf6f86..ec1e607f11 100644 --- a/label/storage.go +++ b/label/storage.go @@ -66,12 +66,6 @@ func (s *Store) generateSafeID(ctx context.Context, tx kv.Tx, bucket []byte) (in for i := 0; i < MaxIDGenerationN; i++ { id := s.IDGenerator.ID() - // TODO: this is probably unnecessary but for testing we need to keep it in. - // After KV is cleaned out we can update the tests and remove this. - if id < ReservedIDs { - continue - } - err := s.uniqueID(ctx, tx, bucket, id) if err == nil { return id, nil diff --git a/label/storage_label.go b/label/storage_label.go index f63d358c18..8a0079db48 100644 --- a/label/storage_label.go +++ b/label/storage_label.go @@ -12,19 +12,11 @@ import ( func (s *Store) CreateLabel(ctx context.Context, tx kv.Tx, l *influxdb.Label) error { // if the provided ID is invalid, or already maps to an existing Auth, then generate a new one - if !l.ID.Valid() { - id, err := s.generateSafeID(ctx, tx, labelBucket) - if err != nil { - return nil - } - l.ID = id - } else if err := uniqueID(ctx, tx, l.ID); err != nil { - id, err := s.generateSafeID(ctx, tx, labelBucket) - if err != nil { - return nil - } - l.ID = id + id, err := s.generateSafeID(ctx, tx, labelBucket) + if err != nil { + return nil } + l.ID = id v, err := json.Marshal(l) if err != nil { @@ -473,29 +465,3 @@ func forEachLabel(ctx context.Context, tx kv.Tx, fn func(*influxdb.Label) bool) return cur.Close() } - -// uniqueID returns nil if the ID provided is unique, returns an error otherwise -func uniqueID(ctx context.Context, tx kv.Tx, id influxdb.ID) error { - encodedID, err := id.Encode() - if err != nil { - return influxdb.ErrInvalidID - } - - b, err := tx.Bucket(labelBucket) - if err != nil { - return ErrInternalServiceError(err) - } - - _, err = b.Get(encodedID) - // if not found then the ID is unique - if kv.IsNotFound(err) { - return nil - } - // no error means this is not unique - if err == nil { - return kv.NotUniqueError - } - - // any other error is some sort of internal server error - return kv.UnexpectedIndexError(err) -} diff --git a/label/storage_test.go b/label/storage_test.go index c64158dd40..042b89fc43 100644 --- a/label/storage_test.go +++ b/label/storage_test.go @@ -11,35 +11,38 @@ import ( "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration/all" "github.com/influxdata/influxdb/v2/label" + "github.com/influxdata/influxdb/v2/mock" "go.uber.org/zap/zaptest" ) func TestLabels(t *testing.T) { setup := func(t *testing.T, store *label.Store, tx kv.Tx) { for i := 1; i <= 10; i++ { - err := store.CreateLabel(context.Background(), tx, &influxdb.Label{ - ID: influxdb.ID(i), - Name: fmt.Sprintf("labelname%d", i), - OrgID: influxdb.ID(i), - }) + mock.SetIDForFunc(&store.IDGenerator, influxdb.ID(i), func() { + err := store.CreateLabel(context.Background(), tx, &influxdb.Label{ + Name: fmt.Sprintf("labelname%d", i), + OrgID: influxdb.ID(i), + }) - if err != nil { - t.Fatal(err) - } + if err != nil { + t.Fatal(err) + } + }) } } setupForList := func(t *testing.T, store *label.Store, tx kv.Tx) { setup(t, store, tx) - err := store.CreateLabel(context.Background(), tx, &influxdb.Label{ - ID: influxdb.ID(11), - Name: fmt.Sprintf("labelname%d", 11), - OrgID: influxdb.ID(5), + mock.SetIDForFunc(&store.IDGenerator, influxdb.ID(11), func() { + err := store.CreateLabel(context.Background(), tx, &influxdb.Label{ + Name: fmt.Sprintf("labelname%d", 11), + OrgID: influxdb.ID(5), + }) + if err != nil { + t.Fatal(err) + } }) - if err != nil { - t.Fatal(err) - } } tt := []struct { diff --git a/notification/endpoint/service/service_test.go b/notification/endpoint/service/service_test.go index b2ae35a305..b37975b39d 100644 --- a/notification/endpoint/service/service_test.go +++ b/notification/endpoint/service/service_test.go @@ -13,8 +13,10 @@ import ( "github.com/influxdata/influxdb/v2/notification/endpoint" "github.com/influxdata/influxdb/v2/notification/endpoint/service" "github.com/influxdata/influxdb/v2/pkg/pointer" + "github.com/influxdata/influxdb/v2/secret" "github.com/influxdata/influxdb/v2/tenant" influxTesting "github.com/influxdata/influxdb/v2/testing" + "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest" ) @@ -51,7 +53,9 @@ func newSecretService(t *testing.T, ctx context.Context, logger *zap.Logger, s k } orgID = &org.ID // orgID is generated - return kv.NewService(logger, s) + secretStore, err := secret.NewStore(s) + require.NoError(t, err) + return secret.NewService(secretStore) } // TestEndpointService_cumulativeSecrets tests that secrets are cumulatively added/updated and removed upon delete diff --git a/notification/endpoint/service/store_test.go b/notification/endpoint/service/store_test.go index e7fb7468e1..7531f2ed57 100644 --- a/notification/endpoint/service/store_test.go +++ b/notification/endpoint/service/store_test.go @@ -14,7 +14,9 @@ import ( "github.com/influxdata/influxdb/v2/kv/migration/all" "github.com/influxdata/influxdb/v2/notification/endpoint/service" endpointsTesting "github.com/influxdata/influxdb/v2/notification/endpoint/service/testing" + "github.com/influxdata/influxdb/v2/secret" "github.com/influxdata/influxdb/v2/tenant" + "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -80,7 +82,6 @@ func initInmemNotificationEndpointService(f endpointsTesting.NotificationEndpoin func initNotificationEndpointService(s kv.SchemaStore, f endpointsTesting.NotificationEndpointFields, t *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()) { ctx := context.Background() - logger := zaptest.NewLogger(t) tenantStore := tenant.NewStore(s) if f.IDGenerator != nil { @@ -90,7 +91,9 @@ func initNotificationEndpointService(s kv.SchemaStore, f endpointsTesting.Notifi tenantSvc := tenant.NewService(tenantStore) - secretSvc := kv.NewService(logger, s) + secretStore, err := secret.NewStore(s) + require.NoError(t, err) + secretSvc := secret.NewService(secretStore) store := service.NewStore(s) store.IDGenerator = f.IDGenerator diff --git a/notification/endpoint/testing/service.go b/notification/endpoint/testing/service.go new file mode 100644 index 0000000000..fdc6d9448c --- /dev/null +++ b/notification/endpoint/testing/service.go @@ -0,0 +1,1709 @@ +package testing + +import ( + "context" + "fmt" + "net/http" + "sort" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + influxdb "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/mock" + "github.com/influxdata/influxdb/v2/notification/endpoint" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + oneID = influxdb.ID(iota + 1) + twoID + threeID + fourID + fiveID + sixID +) + +var ( + fakeDate = time.Date(2006, 5, 4, 1, 2, 3, 0, time.UTC) + fakeGenerator = mock.TimeGenerator{FakeValue: fakeDate} + timeGen1 = mock.TimeGenerator{FakeValue: time.Date(2006, time.July, 13, 4, 19, 10, 0, time.UTC)} + timeGen2 = mock.TimeGenerator{FakeValue: time.Date(2006, time.July, 14, 5, 23, 53, 10, time.UTC)} +) + +// NotificationEndpointFields includes prepopulated data for mapping tests. +type NotificationEndpointFields struct { + IDGenerator influxdb.IDGenerator + TimeGenerator influxdb.TimeGenerator + NotificationEndpoints []influxdb.NotificationEndpoint + Orgs []*influxdb.Organization +} + +var notificationEndpointCmpOptions = cmp.Options{ + cmp.Transformer("Sort", func(in []influxdb.NotificationEndpoint) []influxdb.NotificationEndpoint { + out := append([]influxdb.NotificationEndpoint(nil), in...) + sort.Slice(out, func(i, j int) bool { + return out[i].GetID() > out[j].GetID() + }) + return out + }), +} + +// NotificationEndpointService tests all the service functions. +func NotificationEndpointService( + init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), t *testing.T, +) { + tests := []struct { + name string + fn func(init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), + t *testing.T) + }{ + { + name: "CreateNotificationEndpoint", + fn: CreateNotificationEndpoint, + }, + { + name: "FindNotificationEndpointByID", + fn: FindNotificationEndpointByID, + }, + { + name: "FindNotificationEndpoints", + fn: FindNotificationEndpoints, + }, + { + name: "UpdateNotificationEndpoint", + fn: UpdateNotificationEndpoint, + }, + { + name: "PatchNotificationEndpoint", + fn: PatchNotificationEndpoint, + }, + { + name: "DeleteNotificationEndpoint", + fn: DeleteNotificationEndpoint, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt := tt + t.Parallel() + tt.fn(init, t) + }) + } +} + +// CreateNotificationEndpoint testing. +func CreateNotificationEndpoint( + init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), + t *testing.T, +) { + type args struct { + notificationEndpoint influxdb.NotificationEndpoint + userID influxdb.ID + } + type wants struct { + err error + notificationEndpoints []influxdb.NotificationEndpoint + } + + tests := []struct { + name string + fields NotificationEndpointFields + args args + wants wants + }{ + { + name: "basic create notification endpoint", + fields: NotificationEndpointFields{ + IDGenerator: mock.NewStaticIDGenerator(twoID), + TimeGenerator: fakeGenerator, + Orgs: []*influxdb.Organization{ + {ID: fourID, Name: "org1"}, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{}, + }, + args: args{ + userID: sixID, + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{ + Value: strPtr("pagerduty secret2"), + }, + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: fakeDate, + UpdatedAt: fakeDate, + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{ + Key: fmt.Sprintf("%s-routing-key", twoID), + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, secretSVC, done := init(tt.fields, t) + defer done() + + ctx := context.Background() + err := s.CreateNotificationEndpoint(ctx, tt.args.notificationEndpoint, tt.args.userID) + ErrorsEqual(t, err, tt.wants.err) + + filter := influxdb.NotificationEndpointFilter{} + edps, _, err := s.FindNotificationEndpoints(ctx, filter) + if err != nil { + t.Fatalf("failed to retrieve notification endpoints: %v", err) + } + if diff := cmp.Diff(edps, tt.wants.notificationEndpoints, notificationEndpointCmpOptions...); diff != "" { + t.Errorf("notificationEndpoints are different -got/+want\ndiff %s", diff) + } + + for _, edp := range tt.wants.notificationEndpoints { + secrets, err := secretSVC.GetSecretKeys(ctx, edp.GetOrgID()) + if err != nil { + t.Errorf("failed to retrieve secrets for endpoint: %v", err) + } + for _, expected := range edp.SecretFields() { + assert.Contains(t, secrets, expected.Key) + } + } + }) + } +} + +// FindNotificationEndpointByID testing. +func FindNotificationEndpointByID( + init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), + t *testing.T, +) { + type args struct { + id influxdb.ID + } + type wants struct { + err *influxdb.Error + notificationEndpoint influxdb.NotificationEndpoint + } + + tests := []struct { + name string + fields NotificationEndpointFields + args args + wants wants + }{ + { + name: "bad id", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{ + Key: fmt.Sprintf("%s-token", oneID), + }, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{ + Key: fmt.Sprintf("%s-routing-key", twoID), + }, + }, + }, + }, + args: args{ + id: influxdb.ID(0), + }, + wants: wants{ + err: &influxdb.Error{ + Code: influxdb.EInvalid, + Msg: "no key was provided for notification endpoint", + }, + }, + }, + { + name: "not found", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + id: threeID, + }, + wants: wants{ + err: &influxdb.Error{ + Code: influxdb.ENotFound, + Msg: "notification endpoint not found", + }, + }, + }, + { + name: "basic find telegraf config by id", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + id: twoID, + }, + wants: wants{ + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _, done := init(tt.fields, t) + defer done() + ctx := context.Background() + + edp, err := s.FindNotificationEndpointByID(ctx, tt.args.id) + influxErrsEqual(t, tt.wants.err, err) + if diff := cmp.Diff(edp, tt.wants.notificationEndpoint, notificationEndpointCmpOptions...); diff != "" { + t.Errorf("notification endpoint is different -got/+want\ndiff %s", diff) + } + }) + } +} + +// FindNotificationEndpoints testing +func FindNotificationEndpoints( + init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), + t *testing.T, +) { + type args struct { + filter influxdb.NotificationEndpointFilter + opts influxdb.FindOptions + } + + type wants struct { + notificationEndpoints []influxdb.NotificationEndpoint + err error + } + tests := []struct { + name string + fields NotificationEndpointFields + args args + wants wants + }{ + { + name: "find nothing (empty set)", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{}, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{}, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{}, + }, + }, + { + name: "find all notification endpoints", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + }, + { + name: "filter by organization id only", + fields: NotificationEndpointFields{ + Orgs: []*influxdb.Organization{ + { + ID: oneID, + Name: "org1", + }, + { + ID: fourID, + Name: "org4", + }, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(oneID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty2.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{ + OrgID: idPtr(oneID), + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(oneID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty2.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + }, + { + name: "find options limit", + fields: NotificationEndpointFields{ + Orgs: []*influxdb.Organization{ + { + ID: oneID, + Name: "org1", + }, + { + ID: fourID, + Name: "org4", + }, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fiveID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp4", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{ + Org: strPtr("org4"), + }, + opts: influxdb.FindOptions{ + Limit: 2, + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + }, + }, + }, + { + name: "find options offset", + fields: NotificationEndpointFields{ + Orgs: []*influxdb.Organization{ + { + ID: oneID, + Name: "org1", + }, + { + ID: fourID, + Name: "org4", + }, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{ + Org: strPtr("org4"), + }, + opts: influxdb.FindOptions{ + Offset: 1, + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + }, + { + name: "find options offset", + fields: NotificationEndpointFields{ + Orgs: []*influxdb.Organization{ + { + ID: oneID, + Name: "org1", + }, + { + ID: fourID, + Name: "org4", + }, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{ + Org: strPtr("org4"), + }, + opts: influxdb.FindOptions{ + Limit: 1, + Offset: 1, + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + }, + }, + }, + { + name: "find by id", + fields: NotificationEndpointFields{ + Orgs: []*influxdb.Organization{ + { + ID: oneID, + Name: "org1", + }, + { + ID: fourID, + Name: "org4", + }, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(oneID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{ + ID: idPtr(fourID), + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(fourID), + OrgID: idPtr(oneID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", fourID)}, + }, + }, + }, + }, + { + name: "look for organization not bound to any notification endpoint", + fields: NotificationEndpointFields{ + Orgs: []*influxdb.Organization{ + { + ID: oneID, + Name: "org1", + }, + { + ID: fourID, + Name: "org4", + }, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(threeID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", threeID)}, + }, + }, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{ + OrgID: idPtr(oneID), + }, + }, + wants: wants{ + notificationEndpoints: []influxdb.NotificationEndpoint{}, + }, + }, + { + name: "find nothing", + fields: NotificationEndpointFields{ + Orgs: []*influxdb.Organization{ + { + ID: oneID, + Name: "org1", + }, + { + ID: fourID, + Name: "org4", + }, + }, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp1", + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.HTTP{ + Base: endpoint.Base{ + ID: idPtr(twoID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp2", + }, + URL: "example-webhook.com", + Method: http.MethodGet, + AuthMethod: "none", + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(threeID), + OrgID: idPtr(fourID), + Status: influxdb.Active, + Name: "edp3", + }, + ClientURL: "example-pagerduty.com", RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", threeID)}, + }, + }, + }, + args: args{ + filter: influxdb.NotificationEndpointFilter{ + ID: idPtr(fiveID), + }, + }, + wants: wants{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _, done := init(tt.fields, t) + defer done() + ctx := context.Background() + + edps, n, err := s.FindNotificationEndpoints(ctx, tt.args.filter, tt.args.opts) + ErrorsEqual(t, err, tt.wants.err) + if n != len(tt.wants.notificationEndpoints) { + t.Fatalf("notification endpoints length is different got %d, want %d", n, len(tt.wants.notificationEndpoints)) + } + + if diff := cmp.Diff(edps, tt.wants.notificationEndpoints, notificationEndpointCmpOptions...); diff != "" { + t.Errorf("notification endpoints are different -got/+want\ndiff %s", diff) + } + }) + } +} + +// UpdateNotificationEndpoint testing. +func UpdateNotificationEndpoint( + init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), + t *testing.T, +) { + type args struct { + userID influxdb.ID + orgID influxdb.ID + id influxdb.ID + notificationEndpoint influxdb.NotificationEndpoint + } + + type wants struct { + notificationEndpoint influxdb.NotificationEndpoint + err *influxdb.Error + } + tests := []struct { + name string + fields NotificationEndpointFields + args args + wants wants + }{ + { + name: "can't find the id", + fields: NotificationEndpointFields{ + TimeGenerator: fakeGenerator, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + userID: sixID, + id: fourID, + orgID: fourID, + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Inactive, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: "pager-duty-routing-key-2"}, + }, + }, + wants: wants{ + err: &influxdb.Error{ + Code: influxdb.ENotFound, + Msg: `notification endpoint not found for key "0000000000000004"`, + }, + }, + }, + { + name: "regular update", + fields: NotificationEndpointFields{ + TimeGenerator: fakeGenerator, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + userID: sixID, + id: twoID, + orgID: fourID, + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name3", + OrgID: idPtr(fourID), + Status: influxdb.Inactive, + }, + ClientURL: "example-pagerduty2.com", + RoutingKey: influxdb.SecretField{Value: strPtr("secret value")}, + }, + }, + wants: wants{ + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name3", + OrgID: idPtr(fourID), + Status: influxdb.Inactive, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: fakeDate, + }, + }, + ClientURL: "example-pagerduty2.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + { + name: "update secret", + fields: NotificationEndpointFields{ + TimeGenerator: fakeGenerator, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + userID: sixID, + id: twoID, + orgID: fourID, + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name3", + OrgID: idPtr(fourID), + Status: influxdb.Inactive, + }, + ClientURL: "example-pagerduty2.com", + RoutingKey: influxdb.SecretField{ + Value: strPtr("pager-duty-value2"), + }, + }, + }, + wants: wants{ + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name3", + OrgID: idPtr(fourID), + Status: influxdb.Inactive, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: fakeDate, + }, + }, + ClientURL: "example-pagerduty2.com", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, secretSVC, done := init(tt.fields, t) + defer done() + ctx := context.Background() + + edp, err := s.UpdateNotificationEndpoint(ctx, tt.args.id, tt.args.notificationEndpoint, tt.args.userID) + if err != nil { + require.Equal(t, tt.wants.err, err) + return + } + + if tt.wants.notificationEndpoint != nil { + secrets, err := secretSVC.GetSecretKeys(ctx, edp.GetOrgID()) + if err != nil { + t.Errorf("failed to retrieve secrets for endpoint: %v", err) + } + for _, actual := range edp.SecretFields() { + assert.Contains(t, secrets, actual.Key) + } + + actual, ok := edp.(*endpoint.PagerDuty) + require.Truef(t, ok, "did not get a pager duty endpoint; got: %#v", edp) + wanted := tt.wants.notificationEndpoint.(*endpoint.PagerDuty) + + wb, ab := wanted.Base, actual.Base + require.NotZero(t, ab.CRUDLog) + wb.CRUDLog, ab.CRUDLog = influxdb.CRUDLog{}, influxdb.CRUDLog{} // zero out times + assert.Equal(t, wb, ab) + assert.Equal(t, wanted.ClientURL, actual.ClientURL) + assert.NotEqual(t, wanted.RoutingKey, actual.RoutingKey) + } + }) + } +} + +// PatchNotificationEndpoint testing. +func PatchNotificationEndpoint( + init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), + t *testing.T, +) { + + name3 := "name2" + status3 := influxdb.Inactive + + type args struct { + //userID influxdb.ID + id influxdb.ID + upd influxdb.NotificationEndpointUpdate + } + + type wants struct { + notificationEndpoint influxdb.NotificationEndpoint + err *influxdb.Error + } + tests := []struct { + name string + fields NotificationEndpointFields + args args + wants wants + }{ + { + name: "can't find the id", + fields: NotificationEndpointFields{ + TimeGenerator: fakeGenerator, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + id: fourID, + upd: influxdb.NotificationEndpointUpdate{ + Name: &name3, + Status: &status3, + }, + }, + wants: wants{ + err: &influxdb.Error{ + Code: influxdb.ENotFound, + Msg: "notification endpoint not found", + }, + }, + }, + { + name: "regular update", + fields: NotificationEndpointFields{ + TimeGenerator: fakeGenerator, + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + Status: influxdb.Active, + OrgID: idPtr(fourID), + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + Status: influxdb.Active, + OrgID: idPtr(fourID), + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + id: twoID, + upd: influxdb.NotificationEndpointUpdate{ + Name: &name3, + Status: &status3, + }, + }, + wants: wants{ + notificationEndpoint: &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: name3, + Status: status3, + OrgID: idPtr(fourID), + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: fakeDate, + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _, done := init(tt.fields, t) + defer done() + ctx := context.Background() + + edp, err := s.PatchNotificationEndpoint(ctx, tt.args.id, tt.args.upd) + if err != nil { + if tt.wants.err == nil { + require.NoError(t, err) + } + iErr, ok := err.(*influxdb.Error) + require.True(t, ok, err) + assert.Equal(t, tt.wants.err.Code, iErr.Code) + return + } + if diff := cmp.Diff(edp, tt.wants.notificationEndpoint, notificationEndpointCmpOptions...); tt.wants.err == nil && diff != "" { + t.Errorf("notificationEndpoints are different -got/+want\ndiff %s", diff) + } + }) + } +} + +// DeleteNotificationEndpoint testing. +func DeleteNotificationEndpoint( + init func(NotificationEndpointFields, *testing.T) (influxdb.NotificationEndpointService, influxdb.SecretService, func()), + t *testing.T, +) { + type args struct { + id influxdb.ID + orgID influxdb.ID + userID influxdb.ID + } + + type wants struct { + notificationEndpoints []influxdb.NotificationEndpoint + secretFlds []influxdb.SecretField + orgID influxdb.ID + err *influxdb.Error + } + tests := []struct { + name string + fields NotificationEndpointFields + args args + wants wants + }{ + { + name: "bad id", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + id: influxdb.ID(0), + orgID: fourID, + userID: sixID, + }, + wants: wants{ + err: &influxdb.Error{ + Code: influxdb.EInvalid, + Msg: "no key was provided for notification endpoint", + }, + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + }, + { + name: "none existing endpoint", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + id: fourID, + orgID: fourID, + userID: sixID, + }, + wants: wants{ + err: &influxdb.Error{ + Code: influxdb.ENotFound, + Msg: "notification endpoint not found", + }, + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + }, + { + name: "regular delete", + fields: NotificationEndpointFields{ + NotificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: idPtr(twoID), + Name: "name2", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + ClientURL: "example-pagerduty.com", + RoutingKey: influxdb.SecretField{Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + }, + }, + args: args{ + id: twoID, + orgID: fourID, + userID: sixID, + }, + wants: wants{ + secretFlds: []influxdb.SecretField{ + {Key: fmt.Sprintf("%s-routing-key", twoID)}, + }, + orgID: fourID, + notificationEndpoints: []influxdb.NotificationEndpoint{ + &endpoint.Slack{ + Base: endpoint.Base{ + ID: idPtr(oneID), + Name: "name1", + OrgID: idPtr(fourID), + Status: influxdb.Active, + CRUDLog: influxdb.CRUDLog{ + CreatedAt: timeGen1.Now(), + UpdatedAt: timeGen2.Now(), + }, + }, + URL: "example-slack.com", + Token: influxdb.SecretField{Key: fmt.Sprintf("%s-token", oneID)}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, secretSVC, done := init(tt.fields, t) + defer done() + + ctx := context.Background() + flds, orgID, err := s.DeleteNotificationEndpoint(ctx, tt.args.id) + influxErrsEqual(t, tt.wants.err, err) + if diff := cmp.Diff(flds, tt.wants.secretFlds); diff != "" { + t.Errorf("delete notification endpoint secret fields are different -got/+want\ndiff %s", diff) + } + if diff := cmp.Diff(orgID, tt.wants.orgID); diff != "" { + t.Errorf("delete notification endpoint org id is different -got/+want\ndiff %s", diff) + } + + filter := influxdb.NotificationEndpointFilter{} + edps, n, err := s.FindNotificationEndpoints(ctx, filter) + if err != nil && tt.wants.err == nil { + t.Fatalf("expected errors to be nil got '%v'", err) + } + + if n != len(tt.wants.notificationEndpoints) { + t.Fatalf("notification endpoints length is different got %d, want %d", n, len(tt.wants.notificationEndpoints)) + } + if diff := cmp.Diff(edps, tt.wants.notificationEndpoints, notificationEndpointCmpOptions...); diff != "" { + t.Errorf("notification endpoints are different -got/+want\ndiff %s", diff) + } + + var deletedEndpoint influxdb.NotificationEndpoint + for _, ne := range tt.fields.NotificationEndpoints { + if ne.GetID() == tt.args.id { + deletedEndpoint = ne + break + } + } + if deletedEndpoint == nil { + return + } + + secrets, err := secretSVC.GetSecretKeys(ctx, deletedEndpoint.GetOrgID()) + require.NoError(t, err) + for _, deleted := range deletedEndpoint.SecretFields() { + assert.NotContains(t, secrets, deleted.Key) + } + }) + } +} + +func influxErrsEqual(t *testing.T, expected *influxdb.Error, actual error) { + t.Helper() + + if expected != nil { + require.Error(t, actual) + } + + if actual == nil { + return + } + + if expected == nil { + require.NoError(t, actual) + return + } + iErr, ok := actual.(*influxdb.Error) + require.True(t, ok) + assert.Equal(t, expected.Code, iErr.Code) + assert.Truef(t, strings.HasPrefix(iErr.Error(), expected.Error()), "expected: %s got err: %s", expected.Error(), actual.Error()) +} + +func idPtr(id influxdb.ID) *influxdb.ID { + return &id +} + +func strPtr(s string) *string { return &s } + +// ErrorsEqual checks to see if the provided errors are equivalent. +func ErrorsEqual(t *testing.T, actual, expected error) { + t.Helper() + if expected == nil && actual == nil { + return + } + + if expected == nil && actual != nil { + t.Errorf("unexpected error %s", actual.Error()) + } + + if expected != nil && actual == nil { + t.Errorf("expected error %s but received nil", expected.Error()) + } + + if influxdb.ErrorCode(expected) != influxdb.ErrorCode(actual) { + t.Logf("\nexpected: %v\nactual: %v\n\n", expected, actual) + t.Errorf("expected error code %q but received %q", influxdb.ErrorCode(expected), influxdb.ErrorCode(actual)) + } + + if influxdb.ErrorMessage(expected) != influxdb.ErrorMessage(actual) { + t.Logf("\nexpected: %v\nactual: %v\n\n", expected, actual) + t.Errorf("expected error message %q but received %q", influxdb.ErrorMessage(expected), influxdb.ErrorMessage(actual)) + } +} diff --git a/notification/rule/service/service_test.go b/notification/rule/service/service_test.go index 490f1e0d03..a84d589aad 100644 --- a/notification/rule/service/service_test.go +++ b/notification/rule/service/service_test.go @@ -16,7 +16,9 @@ import ( endpointservice "github.com/influxdata/influxdb/v2/notification/endpoint/service" _ "github.com/influxdata/influxdb/v2/fluxinit/static" "github.com/influxdata/influxdb/v2/query/fluxlang" + "github.com/influxdata/influxdb/v2/secret" "github.com/influxdata/influxdb/v2/tenant" + "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -55,7 +57,13 @@ func TestBoltNotificationRuleStore(t *testing.T) { func initNotificationRuleStore(s kv.Store, f NotificationRuleFields, t *testing.T) (influxdb.NotificationRuleStore, influxdb.TaskService, func()) { logger := zaptest.NewLogger(t) - kvsvc := kv.NewService(logger, s, kv.ServiceConfig{ + + var ( + tenantStore = tenant.NewStore(s) + tenantSvc = tenant.NewService(tenantStore) + ) + + kvsvc := kv.NewService(logger, s, tenantSvc, kv.ServiceConfig{ FluxLanguageService: fluxlang.DefaultService, }) kvsvc.IDGenerator = f.IDGenerator @@ -64,15 +72,14 @@ func initNotificationRuleStore(s kv.Store, f NotificationRuleFields, t *testing. kvsvc.TimeGenerator = influxdb.RealTimeGenerator{} } - var ( - tenantStore = tenant.NewStore(s) - tenantSvc = tenant.NewService(tenantStore) - ) + secretStore, err := secret.NewStore(s) + require.NoError(t, err) + secretSvc := secret.NewService(secretStore) endpStore := endpointservice.NewStore(s) endpStore.IDGenerator = f.IDGenerator endpStore.TimeGenerator = f.TimeGenerator - endp := endpointservice.New(endpStore, kvsvc) + endp := endpointservice.New(endpStore, secretSvc) svc, err := New(logger, s, kvsvc, tenantSvc, endp) if err != nil { diff --git a/source/bucket.go b/source/bucket.go index fa05ae8dfc..931491017f 100644 --- a/source/bucket.go +++ b/source/bucket.go @@ -6,6 +6,7 @@ import ( platform "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/http" "github.com/influxdata/influxdb/v2/http/influxdb" + "github.com/influxdata/influxdb/v2/tenant" ) // NewBucketService creates a bucket service from a source. @@ -21,7 +22,7 @@ func NewBucketService(s *platform.Source) (platform.BucketService, error) { if err != nil { return nil, err } - return &http.BucketService{Client: httpClient}, nil + return &tenant.BucketClientService{Client: httpClient}, nil case platform.V1SourceType: return &influxdb.BucketService{Source: s}, nil } diff --git a/storage/bucket_service_test.go b/storage/bucket_service_test.go index 2935141eb5..68814de97d 100644 --- a/storage/bucket_service_test.go +++ b/storage/bucket_service_test.go @@ -7,10 +7,10 @@ import ( "github.com/golang/mock/gomock" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/inmem" - "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/kv/migration/all" "github.com/influxdata/influxdb/v2/storage" "github.com/influxdata/influxdb/v2/storage/mocks" + "github.com/influxdata/influxdb/v2/tenant" "go.uber.org/zap/zaptest" ) @@ -25,9 +25,8 @@ func TestBucketService(t *testing.T) { engine := mocks.NewMockEngineSchema(ctrl) - inmemService := newInMemKVSVC(t) - logger := zaptest.NewLogger(t) + inmemService := newTenantService(t) service := storage.NewBucketService(logger, inmemService, engine) if err := service.DeleteBucket(context.TODO(), *i); err == nil { @@ -54,7 +53,7 @@ func TestBucketService(t *testing.T) { } } -func newInMemKVSVC(t *testing.T) *kv.Service { +func newTenantService(t *testing.T) *tenant.Service { t.Helper() logger := zaptest.NewLogger(t) @@ -63,5 +62,5 @@ func newInMemKVSVC(t *testing.T) *kv.Service { t.Fatal(err) } - return kv.NewService(logger, store) + return tenant.NewService(tenant.NewStore(store)) } diff --git a/task/backend/analytical_storage_test.go b/task/backend/analytical_storage_test.go index d6e891beb8..1dd41d40ab 100644 --- a/task/backend/analytical_storage_test.go +++ b/task/backend/analytical_storage_test.go @@ -44,10 +44,6 @@ func TestAnalyticalStore(t *testing.T) { t.Fatal(err) } - svc := kv.NewService(logger, store, kv.ServiceConfig{ - FluxLanguageService: fluxlang.DefaultService, - }) - tenantStore := tenant.NewStore(store) ts := tenant.NewService(tenantStore) @@ -55,6 +51,10 @@ func TestAnalyticalStore(t *testing.T) { require.NoError(t, err) authSvc := authorization.NewService(authStore, ts) + svc := kv.NewService(logger, store, ts, kv.ServiceConfig{ + FluxLanguageService: fluxlang.DefaultService, + }) + metaClient := meta.NewClient(meta.NewConfig(), store) require.NoError(t, metaClient.Open()) diff --git a/task/backend/executor/executor_test.go b/task/backend/executor/executor_test.go index fafce7d582..b5e72a60a7 100644 --- a/task/backend/executor/executor_test.go +++ b/task/backend/executor/executor_test.go @@ -13,6 +13,7 @@ import ( "github.com/golang/mock/gomock" "github.com/influxdata/flux" "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" icontext "github.com/influxdata/influxdb/v2/context" "github.com/influxdata/influxdb/v2/inmem" "github.com/influxdata/influxdb/v2/kit/prom" @@ -25,7 +26,9 @@ import ( "github.com/influxdata/influxdb/v2/task/backend" "github.com/influxdata/influxdb/v2/task/backend/executor/mock" "github.com/influxdata/influxdb/v2/task/backend/scheduler" + "github.com/influxdata/influxdb/v2/tenant" "github.com/opentracing/opentracing-go" + "github.com/stretchr/testify/require" "github.com/uber/jaeger-client-go" "go.uber.org/zap/zaptest" ) @@ -67,8 +70,16 @@ func taskExecutorSystem(t *testing.T) tes { ctrl := gomock.NewController(t) ps := mock.NewMockPermissionService(ctrl) ps.EXPECT().FindPermissionForUser(gomock.Any(), gomock.Any()).Return(influxdb.PermissionSet{}, nil).AnyTimes() + + tenantStore := tenant.NewStore(store) + tenantSvc := tenant.NewService(tenantStore) + + authStore, err := authorization.NewStore(store) + require.NoError(t, err) + authSvc := authorization.NewService(authStore, tenantSvc) + var ( - svc = kv.NewService(logger, store, kv.ServiceConfig{ + svc = kv.NewService(logger, store, tenantSvc, kv.ServiceConfig{ FluxLanguageService: fluxlang.DefaultService, }) @@ -81,7 +92,7 @@ func taskExecutorSystem(t *testing.T) tes { metrics: metrics, i: svc, tcs: tcs, - tc: createCreds(t, svc), + tc: createCreds(t, tenantSvc, tenantSvc, authSvc), } } diff --git a/task/backend/executor/support_test.go b/task/backend/executor/support_test.go index 7fea9b2758..56e78cbe3c 100644 --- a/task/backend/executor/support_test.go +++ b/task/backend/executor/support_test.go @@ -15,7 +15,6 @@ import ( "github.com/influxdata/flux/runtime" "github.com/influxdata/flux/values" "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/query" _ "github.com/influxdata/influxdb/v2/fluxinit/static" ) @@ -255,16 +254,16 @@ type testCreds struct { Auth *influxdb.Authorization } -func createCreds(t *testing.T, i *kv.Service) testCreds { +func createCreds(t *testing.T, orgSvc influxdb.OrganizationService, userSvc influxdb.UserService, authSvc influxdb.AuthorizationService) testCreds { t.Helper() org := &influxdb.Organization{Name: t.Name() + "-org"} - if err := i.CreateOrganization(context.Background(), org); err != nil { + if err := orgSvc.CreateOrganization(context.Background(), org); err != nil { t.Fatal(err) } user := &influxdb.User{Name: t.Name() + "-user"} - if err := i.CreateUser(context.Background(), user); err != nil { + if err := userSvc.CreateUser(context.Background(), user); err != nil { t.Fatal(err) } @@ -282,7 +281,7 @@ func createCreds(t *testing.T, i *kv.Service) testCreds { Token: "hifriend!", Permissions: []influxdb.Permission{*readPerm, *writePerm}, } - if err := i.CreateAuthorization(context.Background(), auth); err != nil { + if err := authSvc.CreateAuthorization(context.Background(), auth); err != nil { t.Fatal(err) } diff --git a/tenant/http_server_onboarding_test.go b/tenant/http_server_onboarding_test.go index 880a6420aa..0cd6ddf4c0 100644 --- a/tenant/http_server_onboarding_test.go +++ b/tenant/http_server_onboarding_test.go @@ -7,10 +7,11 @@ import ( "github.com/go-chi/chi" "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" ihttp "github.com/influxdata/influxdb/v2/http" - "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/tenant" itesting "github.com/influxdata/influxdb/v2/testing" + "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -24,11 +25,13 @@ func initOnboardHttpService(f itesting.OnboardingFields, t *testing.T) (influxdb storage := tenant.NewStore(s) - authsvc := kv.NewService(zaptest.NewLogger(t), s) - ten := tenant.NewService(storage) - svc := tenant.NewOnboardService(ten, authsvc) + authStore, err := authorization.NewStore(s) + require.NoError(t, err) + authSvc := authorization.NewService(authStore, ten) + + svc := tenant.NewOnboardService(ten, authSvc) ctx := context.Background() if !f.IsOnboarding { diff --git a/tenant/index/index.go b/tenant/index/index.go new file mode 100644 index 0000000000..1c1fee95a5 --- /dev/null +++ b/tenant/index/index.go @@ -0,0 +1,24 @@ +package index + +import ( + "encoding/json" + + "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/kv" +) + +// URMByUserIndeMappingx is the mapping description of an index +// between a user and a URM +var URMByUserIndexMapping = kv.NewIndexMapping( + []byte("userresourcemappingsv1"), + []byte("userresourcemappingsbyuserindexv1"), + func(v []byte) ([]byte, error) { + var urm influxdb.UserResourceMapping + if err := json.Unmarshal(v, &urm); err != nil { + return nil, err + } + + id, _ := urm.UserID.Encode() + return id, nil + }, +) diff --git a/tenant/service_onboarding_test.go b/tenant/service_onboarding_test.go index cfbdb1b756..6a0dd58603 100644 --- a/tenant/service_onboarding_test.go +++ b/tenant/service_onboarding_test.go @@ -2,18 +2,20 @@ package tenant_test import ( "context" - "github.com/influxdata/influxdb/v2/pkg/testing/assert" "testing" "time" + "github.com/influxdata/influxdb/v2/pkg/testing/assert" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" influxdb "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/authorization" icontext "github.com/influxdata/influxdb/v2/context" "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/tenant" influxdbtesting "github.com/influxdata/influxdb/v2/testing" - "go.uber.org/zap/zaptest" ) func TestBoltOnboardingService(t *testing.T) { @@ -36,8 +38,12 @@ func initOnboardingService(s kv.Store, f influxdbtesting.OnboardingFields, t *te storage := tenant.NewStore(s) ten := tenant.NewService(storage) + authStore, err := authorization.NewStore(s) + require.NoError(t, err) + authSvc := authorization.NewService(authStore, ten) + // we will need an auth service as well - svc := tenant.NewOnboardService(ten, kv.NewService(zaptest.NewLogger(t), s)) + svc := tenant.NewOnboardService(ten, authSvc) ctx := context.Background() @@ -58,8 +64,11 @@ func TestOnboardURM(t *testing.T) { storage := tenant.NewStore(s) ten := tenant.NewService(storage) - // we will need an auth service as well - svc := tenant.NewOnboardService(ten, kv.NewService(zaptest.NewLogger(t), s)) + authStore, err := authorization.NewStore(s) + require.NoError(t, err) + authSvc := authorization.NewService(authStore, ten) + + svc := tenant.NewOnboardService(ten, authSvc) ctx := icontext.SetAuthorizer(context.Background(), &influxdb.Authorization{ UserID: 123, @@ -93,8 +102,11 @@ func TestOnboardAuth(t *testing.T) { storage := tenant.NewStore(s) ten := tenant.NewService(storage) - // we will need an auth service as well - svc := tenant.NewOnboardService(ten, kv.NewService(zaptest.NewLogger(t), s)) + authStore, err := authorization.NewStore(s) + require.NoError(t, err) + authSvc := authorization.NewService(authStore, ten) + + svc := tenant.NewOnboardService(ten, authSvc) ctx := icontext.SetAuthorizer(context.Background(), &influxdb.Authorization{ UserID: 123, @@ -112,44 +124,44 @@ func TestOnboardAuth(t *testing.T) { auth := onboard.Auth expectedPerm := []influxdb.Permission{ - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.AuthorizationsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.AuthorizationsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.BucketsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.BucketsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DashboardsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DashboardsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{ID: &onboard.Org.ID, Type: influxdb.OrgsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{ID: &onboard.Org.ID, Type: influxdb.OrgsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SourcesResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SourcesResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TasksResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TasksResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TelegrafsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TelegrafsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.UsersResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.UsersResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.VariablesResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.VariablesResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ScraperResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ScraperResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SecretsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SecretsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.LabelsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.LabelsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ViewsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ViewsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DocumentsResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DocumentsResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationRuleResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationRuleResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationEndpointResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationEndpointResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ChecksResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ChecksResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DBRPResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DBRPResourceType}}, - influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{ID: &onboard.User.ID, Type: influxdb.UsersResourceType}}, - influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{ID: &onboard.User.ID, Type: influxdb.UsersResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.AuthorizationsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.AuthorizationsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.BucketsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.BucketsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DashboardsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DashboardsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{ID: &onboard.Org.ID, Type: influxdb.OrgsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{ID: &onboard.Org.ID, Type: influxdb.OrgsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SourcesResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SourcesResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TasksResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TasksResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TelegrafsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.TelegrafsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.UsersResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.UsersResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.VariablesResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.VariablesResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ScraperResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ScraperResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SecretsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.SecretsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.LabelsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.LabelsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ViewsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ViewsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DocumentsResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DocumentsResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationRuleResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationRuleResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationEndpointResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotificationEndpointResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ChecksResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.ChecksResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DBRPResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DBRPResourceType}}, + {Action: influxdb.ReadAction, Resource: influxdb.Resource{ID: &onboard.User.ID, Type: influxdb.UsersResourceType}}, + {Action: influxdb.WriteAction, Resource: influxdb.Resource{ID: &onboard.User.ID, Type: influxdb.UsersResourceType}}, } if !cmp.Equal(auth.Permissions, expectedPerm) { t.Fatalf("unequal permissions: \n %+v", cmp.Diff(auth.Permissions, expectedPerm)) @@ -162,8 +174,13 @@ func TestOnboardService_RetentionPolicy(t *testing.T) { storage := tenant.NewStore(s) ten := tenant.NewService(storage) + authStore, err := authorization.NewStore(s) + require.NoError(t, err) + + authSvc := authorization.NewService(authStore, ten) + // we will need an auth service as well - svc := tenant.NewOnboardService(ten, kv.NewService(zaptest.NewLogger(t), s)) + svc := tenant.NewOnboardService(ten, authSvc) ctx := icontext.SetAuthorizer(context.Background(), &influxdb.Authorization{ UserID: 123, @@ -171,9 +188,9 @@ func TestOnboardService_RetentionPolicy(t *testing.T) { retention := 72 * time.Hour onboard, err := svc.OnboardInitialUser(ctx, &influxdb.OnboardingRequest{ - User: "name", - Org: "name", - Bucket: "name", + User: "name", + Org: "name", + Bucket: "name", RetentionPeriod: retention, }) diff --git a/tenant/service_op_log.go b/tenant/service_op_log.go new file mode 100644 index 0000000000..8272b76d9d --- /dev/null +++ b/tenant/service_op_log.go @@ -0,0 +1,158 @@ +package tenant + +import ( + "context" + "encoding/json" + "time" + + "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/kv" +) + +const ( + orgOperationLogKeyPrefix = "org" + bucketOperationLogKeyPrefix = "bucket" + userOperationLogKeyPrefix = "user" +) + +// OpLogStore is a type which persists and reports operation log entries on a backing +// kv store transaction. +type OpLogStore interface { + AddLogEntryTx(ctx context.Context, tx kv.Tx, k, v []byte, t time.Time) error + ForEachLogEntryTx(ctx context.Context, tx kv.Tx, k []byte, opts influxdb.FindOptions, fn func([]byte, time.Time) error) error +} + +// OpLogService is a type which stores operation logs for buckets, users and orgs. +type OpLogService struct { + kv kv.Store + + opLogStore OpLogStore + + TimeGenerator influxdb.TimeGenerator +} + +// NewOpLogService constructs and configures a new op log service. +func NewOpLogService(store kv.Store, opLogStore OpLogStore) *OpLogService { + return &OpLogService{ + kv: store, + opLogStore: opLogStore, + TimeGenerator: influxdb.RealTimeGenerator{}, + } +} + +// GetOrganizationOperationLog retrieves a organization operation log. +func (s *OpLogService) GetOrganizationOperationLog(ctx context.Context, id influxdb.ID, opts influxdb.FindOptions) ([]*influxdb.OperationLogEntry, int, error) { + // TODO(desa): might be worthwhile to allocate a slice of size opts.Limit + log := []*influxdb.OperationLogEntry{} + + err := s.kv.View(ctx, func(tx kv.Tx) error { + key, err := encodeOrganizationOperationLogKey(id) + if err != nil { + return err + } + + return s.opLogStore.ForEachLogEntryTx(ctx, tx, key, opts, func(v []byte, t time.Time) error { + e := &influxdb.OperationLogEntry{} + if err := json.Unmarshal(v, e); err != nil { + return err + } + e.Time = t + + log = append(log, e) + + return nil + }) + }) + + if err != nil && err != kv.ErrKeyValueLogBoundsNotFound { + return nil, 0, err + } + + return log, len(log), nil +} + +// GetBucketOperationLog retrieves a buckets operation log. +func (s *OpLogService) GetBucketOperationLog(ctx context.Context, id influxdb.ID, opts influxdb.FindOptions) ([]*influxdb.OperationLogEntry, int, error) { + // TODO(desa): might be worthwhile to allocate a slice of size opts.Limit + log := []*influxdb.OperationLogEntry{} + + err := s.kv.View(ctx, func(tx kv.Tx) error { + key, err := encodeBucketOperationLogKey(id) + if err != nil { + return err + } + + return s.opLogStore.ForEachLogEntryTx(ctx, tx, key, opts, func(v []byte, t time.Time) error { + e := &influxdb.OperationLogEntry{} + if err := json.Unmarshal(v, e); err != nil { + return err + } + e.Time = t + + log = append(log, e) + + return nil + }) + }) + + if err != nil && err != kv.ErrKeyValueLogBoundsNotFound { + return nil, 0, err + } + + return log, len(log), nil +} + +// GetUserOperationLog retrieves a user operation log. +func (s *OpLogService) GetUserOperationLog(ctx context.Context, id influxdb.ID, opts influxdb.FindOptions) ([]*influxdb.OperationLogEntry, int, error) { + // TODO(desa): might be worthwhile to allocate a slice of size opts.Limit + log := []*influxdb.OperationLogEntry{} + + err := s.kv.View(ctx, func(tx kv.Tx) error { + key, err := encodeUserOperationLogKey(id) + if err != nil { + return err + } + + return s.opLogStore.ForEachLogEntryTx(ctx, tx, key, opts, func(v []byte, t time.Time) error { + e := &influxdb.OperationLogEntry{} + if err := json.Unmarshal(v, e); err != nil { + return err + } + e.Time = t + + log = append(log, e) + + return nil + }) + }) + + if err != nil && err != kv.ErrKeyValueLogBoundsNotFound { + return nil, 0, err + } + + return log, len(log), nil +} + +func encodeOrganizationOperationLogKey(id influxdb.ID) ([]byte, error) { + buf, err := id.Encode() + if err != nil { + return nil, err + } + return append([]byte(orgOperationLogKeyPrefix), buf...), nil +} + +func encodeBucketOperationLogKey(id influxdb.ID) ([]byte, error) { + buf, err := id.Encode() + if err != nil { + return nil, err + } + return append([]byte(bucketOperationLogKeyPrefix), buf...), nil +} + +func encodeUserOperationLogKey(id influxdb.ID) ([]byte, error) { + buf, err := id.Encode() + if err != nil { + return nil, err + } + return append([]byte(userOperationLogKeyPrefix), buf...), nil +} diff --git a/tenant/storage.go b/tenant/storage.go index 0dcf4dd411..841786e351 100644 --- a/tenant/storage.go +++ b/tenant/storage.go @@ -9,6 +9,7 @@ import ( "github.com/influxdata/influxdb/v2/kv" "github.com/influxdata/influxdb/v2/rand" "github.com/influxdata/influxdb/v2/snowflake" + "github.com/influxdata/influxdb/v2/tenant/index" ) const MaxIDGenerationN = 100 @@ -35,7 +36,7 @@ func NewStore(kvStore kv.Store, opts ...StoreOption) *Store { now: func() time.Time { return time.Now().UTC() }, - urmByUserIndex: kv.NewIndex(kv.URMByUserIndexMapping, kv.WithIndexReadPathEnabled), + urmByUserIndex: kv.NewIndex(index.URMByUserIndexMapping, kv.WithIndexReadPathEnabled), } for _, opt := range opts { diff --git a/testing/document.go b/testing/document.go deleted file mode 100644 index fef949247c..0000000000 --- a/testing/document.go +++ /dev/null @@ -1,325 +0,0 @@ -package testing - -import ( - "bytes" - "context" - "sort" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/authorizer" - icontext "github.com/influxdata/influxdb/v2/context" - "github.com/influxdata/influxdb/v2/kv" - "github.com/influxdata/influxdb/v2/mock" - "go.uber.org/zap/zaptest" -) - -// NewDocumentIntegrationTest will test the documents related funcs. -func NewDocumentIntegrationTest(store kv.SchemaStore) func(t *testing.T) { - return func(t *testing.T) { - ctx := context.Background() - - kvsvc := kv.NewService(zaptest.NewLogger(t), store) - mockTimeGen := new(mock.TimeGenerator) - - kvsvc.TimeGenerator = mockTimeGen - svc := authorizer.NewDocumentService(kvsvc) - - s, err := svc.CreateDocumentStore(ctx, "templates") - if err != nil { - t.Fatalf("failed to create document store: %v", err) - } - - ss, err := svc.FindDocumentStore(ctx, "templates") - if err != nil { - t.Fatalf("failed to find document store: %v", err) - } - - l1 := &influxdb.Label{Name: "l1", OrgID: MustIDBase16("41a9f7288d4e2d64")} - l2 := &influxdb.Label{Name: "l2", OrgID: MustIDBase16("41a9f7288d4e2d64")} - MustCreateLabels(ctx, kvsvc, l1, l2) - lBad := &influxdb.Label{ID: MustIDBase16(oneID), Name: "bad"} - - o1 := &influxdb.Organization{Name: "foo"} - o2 := &influxdb.Organization{Name: "bar"} - MustCreateOrgs(ctx, kvsvc, o1, o2) - - u1 := &influxdb.User{Name: "yanky"} - u2 := &influxdb.User{Name: "doodle"} - MustCreateUsers(ctx, kvsvc, u1, u2) - - MustMakeUsersOrgOwner(ctx, kvsvc, o1.ID, u1.ID) - MustMakeUsersOrgOwner(ctx, kvsvc, o2.ID, u2.ID) - MustMakeUsersOrgMember(ctx, kvsvc, o1.ID, u2.ID) - - // TODO(desa): test tokens and authorizations as well. - now := time.Now() - s1 := &influxdb.Session{ - CreatedAt: now, - ExpiresAt: now.Add(1 * time.Hour), - UserID: u1.ID, - Permissions: []influxdb.Permission{ - // u1 is owner of o1 - { - Action: influxdb.ReadAction, - Resource: influxdb.Resource{ - OrgID: &o1.ID, - Type: influxdb.DocumentsResourceType, - }, - }, - { - Action: influxdb.WriteAction, - Resource: influxdb.Resource{ - OrgID: &o1.ID, - Type: influxdb.DocumentsResourceType, - }, - }, - }, - } - s2 := &influxdb.Session{ - CreatedAt: now, - ExpiresAt: now.Add(1 * time.Hour), - UserID: u2.ID, - Permissions: []influxdb.Permission{ - // u2 is owner of o2 - { - Action: influxdb.ReadAction, - Resource: influxdb.Resource{ - OrgID: &o2.ID, - Type: influxdb.DocumentsResourceType, - }, - }, - { - Action: influxdb.WriteAction, - Resource: influxdb.Resource{ - OrgID: &o2.ID, - Type: influxdb.DocumentsResourceType, - }, - }, - // u2 is member of o1 - { - Action: influxdb.ReadAction, - Resource: influxdb.Resource{ - OrgID: &o1.ID, - Type: influxdb.DocumentsResourceType, - }, - }, - }, - } - - var d1 *influxdb.Document - var d2 *influxdb.Document - var d3 *influxdb.Document - - t.Run("u1 can create document for o1", func(t *testing.T) { - d1 = &influxdb.Document{ - Meta: influxdb.DocumentMeta{ - Name: "i1", - Type: "type1", - Description: "desc1", - }, - Content: map[string]interface{}{ - "v1": "v1", - }, - Labels: []*influxdb.Label{l1}, - Organizations: map[influxdb.ID]influxdb.UserType{o1.ID: influxdb.Owner}, - } - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s1) - - mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 0, 0, time.UTC) - if err := s.CreateDocument(ctx, d1); err != nil { - t.Errorf("failed to create document: %v", err) - } - }) - - t.Run("u2 cannot create document for o1", func(t *testing.T) { - d2 = &influxdb.Document{ - Meta: influxdb.DocumentMeta{ - Name: "i2", - }, - Content: map[string]interface{}{ - "i2": "i2", - }, - Labels: []*influxdb.Label{l2}, - Organizations: map[influxdb.ID]influxdb.UserType{o1.ID: influxdb.Owner}, - } - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s2) - - mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 1, 0, time.UTC) - if err := s.CreateDocument(ctx, d2); err == nil { - t.Fatalf("should not have been authorized to create document") - } - - mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 1, 0, time.UTC) - d2.Organizations = map[influxdb.ID]influxdb.UserType{o2.ID: influxdb.Owner} - if err := s.CreateDocument(ctx, d2); err != nil { - t.Errorf("should have been authorized to create document: %v", err) - } - }) - - t.Run("u1 cannot create document for o2", func(t *testing.T) { - d3 = &influxdb.Document{ - Meta: influxdb.DocumentMeta{ - Name: "i2", - }, - Content: map[string]interface{}{ - "k2": "v2", - }, - Organizations: map[influxdb.ID]influxdb.UserType{o2.ID: influxdb.Owner}, - } - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s1) - - mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 2, 0, time.UTC) - if err := s.CreateDocument(ctx, d3); err == nil { - t.Errorf("should not have be authorized to create document") - } - }) - - t.Run("can't create document with non existing label", func(t *testing.T) { - d4 := &influxdb.Document{ - Meta: influxdb.DocumentMeta{ - Name: "i4", - }, - Content: map[string]interface{}{ - "k4": "v4", - }, - Labels: []*influxdb.Label{lBad}, - Organizations: map[influxdb.ID]influxdb.UserType{o1.ID: influxdb.Owner}, - } - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s1) - err = s.CreateDocument(ctx, d4) - ErrorsEqual(t, err, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "label not found", - }) - }) - - d1.Meta.CreatedAt = time.Date(2009, 1, 2, 3, 0, 0, 0, time.UTC) - dl1 := new(influxdb.Document) - *dl1 = *d1 - dl1.Labels = append([]*influxdb.Label{}, l1) - - d2.Meta.CreatedAt = time.Date(2009, 1, 2, 3, 0, 1, 0, time.UTC) - dl2 := new(influxdb.Document) - *dl2 = *d2 - dl2.Labels = append([]*influxdb.Label{}, d2.Labels...) - - d3.Meta.CreatedAt = time.Date(2009, 1, 2, 3, 0, 2, 0, time.UTC) - - t.Run("u1 can see only o1s documents by label", func(t *testing.T) { - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s1) - ds, err := ss.FindDocuments( - ctx, - influxdb.WhereOrgID(o1.ID), - influxdb.WhereOrgID(o2.ID), - influxdb.IncludeContent, - influxdb.IncludeLabels, - ) - if err != nil { - t.Fatalf("failed to retrieve documents: %v", err) - } - - if exp, got := []*influxdb.Document{dl1}, ds; !docsEqual(got, exp) { - t.Errorf("documents are different -got/+want\ndiff %s", docsDiff(got, exp)) - } - }) - - t.Run("check not found err", func(t *testing.T) { - _, err := ss.FindDocument(ctx, MustIDBase16(fourID)) - ErrorsEqual(t, err, &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: influxdb.ErrDocumentNotFound, - }) - }) - - t.Run("u2 can see o1 and o2s documents", func(t *testing.T) { - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s2) - ds, err := ss.FindDocuments( - ctx, - influxdb.WhereOrgID(o1.ID), - influxdb.WhereOrgID(o2.ID), - influxdb.IncludeContent, - influxdb.IncludeLabels, - ) - if err != nil { - t.Fatalf("failed to retrieve documents for org1: %v", err) - } - if exp, got := []*influxdb.Document{dl1, dl2}, ds; !docsEqual(exp, got) { - t.Errorf("documents are different -got/+want\ndiff %s", docsDiff(exp, got)) - } - }) - - t.Run("u2 cannot update document d1", func(t *testing.T) { - d := &influxdb.Document{ - ID: d1.ID, - Meta: influxdb.DocumentMeta{ - Name: "updatei1", - }, - Content: map[string]interface{}{ - "updatev1": "updatev1", - }, - } - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s2) - if err := s.UpdateDocument(ctx, d); err == nil { - t.Errorf("should not have been authorized to update document") - return - } - }) - - t.Run("u2 can update document d2", func(t *testing.T) { - d := &influxdb.Document{ - ID: d2.ID, - Meta: influxdb.DocumentMeta{ - Name: "updatei2", - }, - Content: map[string]interface{}{ - "updatev2": "updatev2", - }, - } - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s2) - if err := s.UpdateDocument(ctx, d); err != nil { - t.Errorf("unexpected error updating document: %v", err) - } - }) - - t.Run("u1 can update document d1", func(t *testing.T) { - ctx := context.Background() - ctx = icontext.SetAuthorizer(ctx, s1) - if err := s.DeleteDocuments(ctx); err != nil { - t.Errorf("unexpected error deleteing document: %v", err) - } - }) - - } -} - -func docsEqual(i1, i2 interface{}) bool { - return cmp.Equal(i1, i2, documentCmpOptions...) -} - -func docsDiff(i1, i2 interface{}) string { - return cmp.Diff(i1, i2, documentCmpOptions...) -} - -var documentCmpOptions = cmp.Options{ - cmp.Comparer(func(x, y []byte) bool { - return bytes.Equal(x, y) - }), - cmp.Transformer("Sort", func(in []*influxdb.Document) []*influxdb.Document { - out := append([]*influxdb.Document(nil), in...) // Copy input to avoid mutating it - sort.Slice(out, func(i, j int) bool { - return out[i].ID.String() > out[j].ID.String() - }) - return out - }), -} diff --git a/testing/scraper_target.go b/testing/scraper_target.go index 693d442603..fe69981cff 100644 --- a/testing/scraper_target.go +++ b/testing/scraper_target.go @@ -3,6 +3,7 @@ package testing import ( "bytes" "context" + "fmt" "sort" "testing" @@ -42,13 +43,11 @@ var ( URL: "url3", ID: MustIDBase16(targetThreeID), } - org1 = influxdb.Organization{ - ID: idOne, - Name: "org1", - } - org2 = influxdb.Organization{ - ID: idTwo, - Name: "org2", + newOrg = func(id influxdb.ID) *influxdb.Organization { + return &influxdb.Organization{ + ID: id, + Name: fmt.Sprintf("org%d", int(id)), + } } ) @@ -137,7 +136,7 @@ func AddTarget( fields: TargetFields{ IDGenerator: mock.NewIDGenerator(targetOneID, t), Targets: []*influxdb.ScraperTarget{}, - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, }, args: args{ userID: MustIDBase16(threeID), @@ -166,7 +165,7 @@ func AddTarget( name: "create target with invalid org id", fields: TargetFields{ IDGenerator: mock.NewIDGenerator(targetTwoID, t), - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { Name: "name1", @@ -209,7 +208,7 @@ func AddTarget( name: "create target with invalid bucket id", fields: TargetFields{ IDGenerator: mock.NewIDGenerator(targetTwoID, t), - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { Name: "name1", @@ -262,7 +261,7 @@ func AddTarget( ID: MustIDBase16(targetOneID), }, }, - Organizations: []*influxdb.Organization{&org1, &org2}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1)), newOrg(influxdb.ID(2))}, }, args: args{ userID: MustIDBase16(threeID), @@ -341,8 +340,8 @@ func ListTargets( name: "get all targets", fields: TargetFields{ Organizations: []*influxdb.Organization{ - &org1, - &org2, + newOrg(influxdb.ID(1)), + newOrg(influxdb.ID(2)), }, Targets: []*influxdb.ScraperTarget{ &target1, @@ -365,8 +364,8 @@ func ListTargets( name: "filter by name", fields: TargetFields{ Organizations: []*influxdb.Organization{ - &org1, - &org2, + newOrg(influxdb.ID(1)), + newOrg(influxdb.ID(2)), }, Targets: []*influxdb.ScraperTarget{ &target1, @@ -389,8 +388,8 @@ func ListTargets( name: "filter by id", fields: TargetFields{ Organizations: []*influxdb.Organization{ - &org1, - &org2, + newOrg(influxdb.ID(1)), + newOrg(influxdb.ID(2)), }, Targets: []*influxdb.ScraperTarget{ &target1, @@ -413,8 +412,8 @@ func ListTargets( name: "filter targets by orgID", fields: TargetFields{ Organizations: []*influxdb.Organization{ - &org1, - &org2, + newOrg(influxdb.ID(1)), + newOrg(influxdb.ID(2)), }, Targets: []*influxdb.ScraperTarget{ &target1, @@ -434,37 +433,12 @@ func ListTargets( }, }, }, - { - name: "filter targets by orgID not exist", - fields: TargetFields{ - Organizations: []*influxdb.Organization{ - &org2, - }, - Targets: []*influxdb.ScraperTarget{ - &target1, - &target2, - &target3, - }, - }, - args: args{ - filter: influxdb.ScraperTargetFilter{ - OrgID: idPtr(idOne), - }, - }, - wants: wants{ - targets: []influxdb.ScraperTarget{}, - err: &influxdb.Error{ - Code: influxdb.ENotFound, - Msg: "organization not found", - }, - }, - }, { name: "filter targets by org name", fields: TargetFields{ Organizations: []*influxdb.Organization{ - &org1, - &org2, + newOrg(influxdb.ID(1)), + newOrg(influxdb.ID(2)), }, Targets: []*influxdb.ScraperTarget{ &target1, @@ -488,7 +462,7 @@ func ListTargets( name: "filter targets by org name not exist", fields: TargetFields{ Organizations: []*influxdb.Organization{ - &org1, + newOrg(influxdb.ID(1)), }, Targets: []*influxdb.ScraperTarget{ &target1, @@ -548,7 +522,7 @@ func GetTargetByID( { name: "basic find target by id", fields: TargetFields{ - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { ID: MustIDBase16(targetOneID), @@ -643,7 +617,7 @@ func RemoveTarget(init func(TargetFields, *testing.T) (influxdb.ScraperTargetSto { name: "delete targets using exist id", fields: TargetFields{ - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { ID: MustIDBase16(targetOneID), @@ -674,7 +648,7 @@ func RemoveTarget(init func(TargetFields, *testing.T) (influxdb.ScraperTargetSto { name: "delete targets using id that does not exist", fields: TargetFields{ - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { ID: MustIDBase16(targetOneID), @@ -756,7 +730,7 @@ func UpdateTarget( { name: "update url with blank id", fields: TargetFields{ - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { ID: MustIDBase16(targetOneID), @@ -786,7 +760,7 @@ func UpdateTarget( { name: "update url with non exist id", fields: TargetFields{ - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { ID: MustIDBase16(targetOneID), @@ -817,7 +791,7 @@ func UpdateTarget( { name: "update url", fields: TargetFields{ - Organizations: []*influxdb.Organization{&org1}, + Organizations: []*influxdb.Organization{newOrg(influxdb.ID(1))}, Targets: []*influxdb.ScraperTarget{ { ID: MustIDBase16(targetOneID),