From 16d916a952fbb4f73b314c9af873088ac2607693 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Tue, 20 Oct 2020 14:25:36 +0100 Subject: [PATCH] refactor(kv): delete deprecated kv service code This includes removal of a lot of kv.Service responsibilities. However, it does not finish the re-wiring. It removes documents, telegrafs, notification rules + endpoints, checks, orgs, users, buckets, passwords, urms, labels and authorizations. There are some oustanding pieces that are needed to get kv service compiling (dashboard service urm dependency). Then all the call sites for kv service need updating and the new implementations of telegraf and notification rules + endpoints needed installing (along with any necessary migrations). --- authorizer/task_test.go | 36 +- checks/service_external_test.go | 219 +-- checks/service_test.go | 32 +- cmd/influx/backup.go | 12 +- cmd/influx/bucket.go | 6 +- cmd/influx/main.go | 9 +- cmd/influx/organization.go | 10 +- cmd/influx/restore.go | 20 +- cmd/influx/secret.go | 8 +- cmd/influx/template.go | 4 +- cmd/influx/user.go | 9 +- cmd/influxd/launcher/launcher.go | 60 +- cmd/influxd/launcher/launcher_helpers.go | 13 +- cmd/influxd/launcher/launcher_test.go | 10 +- cmd/influxd/launcher/pkger_test.go | 19 +- cmd/influxd/upgrade/database_test.go | 5 +- cmd/influxd/upgrade/security_test.go | 17 +- cmd/influxd/upgrade/upgrade.go | 32 +- dashboards/service_test.go | 3 +- dashboards/testing/util.go | 51 +- dashboards/transport/http_test.go | 2 +- http/api_handler.go | 28 +- http/api_handler_test.go | 10 +- http/auth_test.go | 28 +- http/bucket_service.go | 752 -------- http/bucket_service_test.go | 1260 ------------ http/client.go | 18 +- http/delete_test.go | 2 + http/document_service.go | 758 -------- http/document_service_test.go | 760 -------- http/document_test.go | 960 --------- http/helpers.go | 49 - http/label_service.go | 6 +- http/label_test.go | 27 +- http/middleware.go | 18 +- http/notification_endpoint_test.go | 20 +- http/notification_rule.go | 8 - http/onboarding.go | 217 --- http/onboarding_test.go | 67 - http/org_service.go | 624 ------ http/org_service_test.go | 426 ---- http/query_handler_test.go | 4 +- http/scraper_service.go | 13 + http/scraper_service_test.go | 23 +- http/source_service.go | 226 ++- http/task_service_test.go | 54 +- http/usage_service.go | 129 -- http/user_test.go | 12 +- http/variable_test.go | 7 +- kv/auth.go | 561 ------ kv/auth_private_test.go | 149 -- kv/auth_test.go | 76 - kv/bucket.go | 914 --------- kv/bucket_test.go | 69 - kv/document.go | 586 ------ kv/document_options.go | 216 --- kv/document_test.go | 17 - kv/errors.go | 23 + kv/initial_migration.go | 30 +- kv/kvlog_test.go | 3 +- kv/label.go | 659 ------- kv/label_test.go | 54 - kv/migration/all/0002_urm_by_user_index.go | 7 +- kv/migration/all/0003_task_owners_test.go | 21 +- kv/onboarding.go | 177 -- kv/onboarding_test.go | 52 - kv/org.go | 727 ------- kv/org_test.go | 55 - kv/passwords.go | 229 --- kv/passwords_test.go | 479 ----- kv/scrapers.go | 28 +- kv/scrapers_test.go | 22 +- kv/secret.go | 255 --- kv/secret_test.go | 43 - kv/service.go | 35 +- kv/service_test.go | 49 - kv/source_test.go | 3 +- kv/task.go | 138 +- kv/task_test.go | 44 +- kv/tenant_test.go | 97 - kv/unique.go | 98 - kv/urm.go | 497 ----- kv/urm_private_test.go | 70 - kv/urm_test.go | 136 -- kv/user.go | 622 ------ kv/user_test.go | 48 - kv/variable.go | 20 +- kv/variable_test.go | 3 +- label/controller.go | 94 - label/controller_test.go | 52 - label/http_client.go | 6 +- label/service_test.go | 13 +- label/storage.go | 6 - label/storage_label.go | 42 +- label/storage_test.go | 33 +- notification/endpoint/service/service_test.go | 6 +- notification/endpoint/service/store_test.go | 7 +- notification/endpoint/testing/service.go | 1709 +++++++++++++++++ notification/rule/service/service_test.go | 19 +- source/bucket.go | 3 +- storage/bucket_service_test.go | 9 +- task/backend/analytical_storage_test.go | 8 +- task/backend/executor/executor_test.go | 15 +- task/backend/executor/support_test.go | 9 +- tenant/http_server_onboarding_test.go | 11 +- tenant/index/index.go | 24 + tenant/service_onboarding_test.go | 115 +- tenant/service_op_log.go | 158 ++ tenant/storage.go | 3 +- testing/document.go | 325 ---- testing/scraper_target.go | 80 +- 111 files changed, 2840 insertions(+), 14302 deletions(-) delete mode 100644 http/bucket_service.go delete mode 100644 http/bucket_service_test.go delete mode 100644 http/document_service.go delete mode 100644 http/document_service_test.go delete mode 100644 http/document_test.go delete mode 100644 http/helpers.go delete mode 100644 http/onboarding.go delete mode 100644 http/onboarding_test.go delete mode 100644 http/org_service.go delete mode 100644 http/org_service_test.go delete mode 100644 http/usage_service.go delete mode 100644 kv/auth.go delete mode 100644 kv/auth_private_test.go delete mode 100644 kv/auth_test.go delete mode 100644 kv/bucket.go delete mode 100644 kv/bucket_test.go delete mode 100644 kv/document.go delete mode 100644 kv/document_options.go delete mode 100644 kv/document_test.go create mode 100644 kv/errors.go delete mode 100644 kv/label.go delete mode 100644 kv/label_test.go delete mode 100644 kv/onboarding.go delete mode 100644 kv/onboarding_test.go delete mode 100644 kv/org.go delete mode 100644 kv/org_test.go delete mode 100644 kv/passwords.go delete mode 100644 kv/passwords_test.go delete mode 100644 kv/secret.go delete mode 100644 kv/secret_test.go delete mode 100644 kv/service_test.go delete mode 100644 kv/tenant_test.go delete mode 100644 kv/unique.go delete mode 100644 kv/urm.go delete mode 100644 kv/urm_private_test.go delete mode 100644 kv/urm_test.go delete mode 100644 kv/user.go delete mode 100644 kv/user_test.go delete mode 100644 label/controller.go delete mode 100644 label/controller_test.go create mode 100644 notification/endpoint/testing/service.go create mode 100644 tenant/index/index.go create mode 100644 tenant/service_op_log.go delete mode 100644 testing/document.go 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),