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).pull/20053/head
parent
aa1cefa9e2
commit
16d916a952
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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, ""),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
561
kv/auth.go
561
kv/auth.go
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
914
kv/bucket.go
914
kv/bucket.go
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
586
kv/document.go
586
kv/document.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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",
|
||||
}
|
|
@ -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"),
|
||||
|
|
|
@ -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 {
|
||||
|
|
659
kv/label.go
659
kv/label.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
177
kv/onboarding.go
177
kv/onboarding.go
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
727
kv/org.go
727
kv/org.go
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
229
kv/passwords.go
229
kv/passwords.go
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
255
kv/secret.go
255
kv/secret.go
|
@ -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)
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
138
kv/task.go
138
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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
98
kv/unique.go
98
kv/unique.go
|
@ -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
|
||||
}
|
497
kv/urm.go
497
kv/urm.go
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
136
kv/urm_test.go
136
kv/urm_test.go
|
@ -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],
|
||||
})
|
||||
}
|
||||
}
|
622
kv/user.go
622
kv/user.go
|
@ -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",
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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...)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue