package tenant import ( "context" "fmt" "sort" "github.com/google/go-cmp/cmp" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kv" "go.uber.org/zap" ) var _ influxdb.TenantService = (*tenantService)(nil) var bucketCmpOptions = cmp.Options{ cmp.Transformer("Sort", func(in []*influxdb.Bucket) []*influxdb.Bucket { out := append([]*influxdb.Bucket(nil), in...) // Copy input to avoid mutating it sort.Slice(out, func(i, j int) bool { return out[i].Name > out[j].Name }) return out }), } // readOnlyStore is a wrapper for kv.Store that ensures that updates are not applied. type readOnlyStore struct { kv.Store } func (r readOnlyStore) Update(context.Context, func(kv.Tx) error) error { return nil } type tenantService struct { log *zap.Logger oldBucketSvc influxdb.BucketService newBucketSvc influxdb.BucketService oldOrgSvc influxdb.OrganizationService newOrgSvc influxdb.OrganizationService oldURMSvc influxdb.UserResourceMappingService newURMSvc influxdb.UserResourceMappingService oldUserSvc influxdb.UserService newUserSvc influxdb.UserService oldPwdSvc influxdb.PasswordsService newPwdSvc influxdb.PasswordsService } // NewReadOnlyStore returns a Store that cannot update the underlying kv.Store. func NewReadOnlyStore(store kv.Store) (*Store, error) { return NewStore(readOnlyStore{Store: store}) } // NewDuplicateReadTenantService returns a tenant service that duplicates the reads to oldSvc and newSvc. // The foreseen use case is to compare two service versions, an old one and a new one. // The resulting influxdb.TenantService: // - forwards writes to the old service; // - reads from the old one, if no error is encountered, it reads from the new one; // - compares the results obtained and logs the difference, if any. func NewDuplicateReadTenantService(log *zap.Logger, oldSvc, newSvc influxdb.TenantService) influxdb.TenantService { return tenantService{ log: log, oldBucketSvc: oldSvc, oldOrgSvc: oldSvc, oldURMSvc: oldSvc, oldUserSvc: oldSvc, oldPwdSvc: oldSvc, newBucketSvc: newSvc, newOrgSvc: newSvc, newURMSvc: newSvc, newUserSvc: newSvc, newPwdSvc: newSvc, } } // NewDuplicateReadBucketService returns a service that duplicates the reads to oldSvc and newSvc. func NewDuplicateReadBucketService(log *zap.Logger, oldSvc, newSvc influxdb.BucketService) influxdb.BucketService { return tenantService{ log: log, oldBucketSvc: oldSvc, newBucketSvc: newSvc, } } // NewDuplicateReadOrganizationService returns a service that duplicates the reads to oldSvc and newSvc. func NewDuplicateReadOrganizationService(log *zap.Logger, oldSvc, newSvc influxdb.OrganizationService) influxdb.OrganizationService { return tenantService{ log: log, oldOrgSvc: oldSvc, newOrgSvc: newSvc, } } // NewDuplicateReadUserResourceMappingService returns a service that duplicates the reads to oldSvc and newSvc. func NewDuplicateReadUserResourceMappingService(log *zap.Logger, oldSvc, newSvc influxdb.UserResourceMappingService) influxdb.UserResourceMappingService { return tenantService{ log: log, oldURMSvc: oldSvc, newURMSvc: newSvc, } } // NewDuplicateReadUserService returns a service that duplicates the reads to oldSvc and newSvc. func NewDuplicateReadUserService(log *zap.Logger, oldSvc, newSvc influxdb.UserService) influxdb.UserService { return tenantService{ log: log, oldUserSvc: oldSvc, newUserSvc: newSvc, } } // NewDuplicateReadPasswordsService returns a service that duplicates the reads to oldSvc and newSvc. func NewDuplicateReadPasswordsService(log *zap.Logger, oldSvc, newSvc influxdb.PasswordsService) influxdb.PasswordsService { return tenantService{ log: log, oldPwdSvc: oldSvc, newPwdSvc: newSvc, } } func (s tenantService) FindBucketByID(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { o, err := s.oldBucketSvc.FindBucketByID(ctx, id) if err != nil { return o, err } n, err := s.newBucketSvc.FindBucketByID(ctx, id) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindBucketByID")) } return o, nil } func (s tenantService) FindBucketByName(ctx context.Context, orgID influxdb.ID, name string) (*influxdb.Bucket, error) { o, err := s.oldBucketSvc.FindBucketByName(ctx, orgID, name) if err != nil { return o, err } n, err := s.newBucketSvc.FindBucketByName(ctx, orgID, name) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindBucketByName")) } return o, nil } func (s tenantService) FindBucket(ctx context.Context, filter influxdb.BucketFilter) (*influxdb.Bucket, error) { o, err := s.oldBucketSvc.FindBucket(ctx, filter) if err != nil { return o, err } n, err := s.newBucketSvc.FindBucket(ctx, filter) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindBucket")) } return o, nil } func (s tenantService) FindBuckets(ctx context.Context, filter influxdb.BucketFilter, opt ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { o, no, err := s.oldBucketSvc.FindBuckets(ctx, filter, opt...) if err != nil { return o, no, err } n, _, err := s.newBucketSvc.FindBuckets(ctx, filter, opt...) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n, bucketCmpOptions); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindBuckets")) } return o, no, nil } func (s tenantService) CreateBucket(ctx context.Context, b *influxdb.Bucket) error { return s.oldBucketSvc.CreateBucket(ctx, b) } func (s tenantService) UpdateBucket(ctx context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { return s.oldBucketSvc.UpdateBucket(ctx, id, upd) } func (s tenantService) DeleteBucket(ctx context.Context, id influxdb.ID) error { return s.oldBucketSvc.DeleteBucket(ctx, id) } func (s tenantService) FindOrganizationByID(ctx context.Context, id influxdb.ID) (*influxdb.Organization, error) { o, err := s.oldOrgSvc.FindOrganizationByID(ctx, id) if err != nil { return o, err } n, err := s.newOrgSvc.FindOrganizationByID(ctx, id) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindOrganizationByID")) } return o, nil } func (s tenantService) FindOrganization(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) { o, err := s.oldOrgSvc.FindOrganization(ctx, filter) if err != nil { return o, err } n, err := s.newOrgSvc.FindOrganization(ctx, filter) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindOrganization")) } return o, nil } func (s tenantService) FindOrganizations(ctx context.Context, filter influxdb.OrganizationFilter, opt ...influxdb.FindOptions) ([]*influxdb.Organization, int, error) { o, no, err := s.oldOrgSvc.FindOrganizations(ctx, filter, opt...) if err != nil { return o, no, err } n, _, err := s.newOrgSvc.FindOrganizations(ctx, filter, opt...) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindOrganizations")) } return o, no, nil } func (s tenantService) CreateOrganization(ctx context.Context, b *influxdb.Organization) error { return s.oldOrgSvc.CreateOrganization(ctx, b) } func (s tenantService) UpdateOrganization(ctx context.Context, id influxdb.ID, upd influxdb.OrganizationUpdate) (*influxdb.Organization, error) { return s.oldOrgSvc.UpdateOrganization(ctx, id, upd) } func (s tenantService) DeleteOrganization(ctx context.Context, id influxdb.ID) error { return s.oldOrgSvc.DeleteOrganization(ctx, id) } func (s tenantService) FindUserResourceMappings(ctx context.Context, filter influxdb.UserResourceMappingFilter, opt ...influxdb.FindOptions) ([]*influxdb.UserResourceMapping, int, error) { o, no, err := s.oldURMSvc.FindUserResourceMappings(ctx, filter, opt...) if err != nil { return o, no, err } n, _, err := s.newURMSvc.FindUserResourceMappings(ctx, filter, opt...) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindUserResourceMappings")) } return o, no, nil } func (s tenantService) CreateUserResourceMapping(ctx context.Context, m *influxdb.UserResourceMapping) error { return s.oldURMSvc.CreateUserResourceMapping(ctx, m) } func (s tenantService) DeleteUserResourceMapping(ctx context.Context, resourceID, userID influxdb.ID) error { return s.oldURMSvc.DeleteUserResourceMapping(ctx, resourceID, userID) } func (s tenantService) FindUserByID(ctx context.Context, id influxdb.ID) (*influxdb.User, error) { o, err := s.oldUserSvc.FindUserByID(ctx, id) if err != nil { return o, err } n, err := s.newUserSvc.FindUserByID(ctx, id) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindUserByID")) } return o, nil } func (s tenantService) FindUser(ctx context.Context, filter influxdb.UserFilter) (*influxdb.User, error) { o, err := s.oldUserSvc.FindUser(ctx, filter) if err != nil { return o, err } n, err := s.newUserSvc.FindUser(ctx, filter) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindUser")) } return o, nil } func (s tenantService) FindUsers(ctx context.Context, filter influxdb.UserFilter, opt ...influxdb.FindOptions) ([]*influxdb.User, int, error) { o, no, err := s.oldUserSvc.FindUsers(ctx, filter, opt...) if err != nil { return o, no, err } n, _, err := s.newUserSvc.FindUsers(ctx, filter, opt...) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } else if diff := cmp.Diff(o, n); diff != "" { s.log.Error(fmt.Sprintf("unexpected read result -old/+new:\n\t%s", diff), zap.String("diff", diff), zap.String("call", "FindUsers")) } return o, no, nil } func (s tenantService) CreateUser(ctx context.Context, u *influxdb.User) error { return s.oldUserSvc.CreateUser(ctx, u) } func (s tenantService) UpdateUser(ctx context.Context, id influxdb.ID, upd influxdb.UserUpdate) (*influxdb.User, error) { return s.oldUserSvc.UpdateUser(ctx, id, upd) } func (s tenantService) DeleteUser(ctx context.Context, id influxdb.ID) error { return s.oldUserSvc.DeleteUser(ctx, id) } func (s tenantService) SetPassword(ctx context.Context, userID influxdb.ID, password string) error { return s.oldPwdSvc.SetPassword(ctx, userID, password) } func (s tenantService) ComparePassword(ctx context.Context, userID influxdb.ID, password string) error { if err := s.oldPwdSvc.ComparePassword(ctx, userID, password); err != nil { return err } err := s.newPwdSvc.ComparePassword(ctx, userID, password) if err != nil { s.log.Error("error in new meta store", zap.Error(err)) } return nil } func (s tenantService) CompareAndSetPassword(ctx context.Context, userID influxdb.ID, old, new string) error { return s.oldPwdSvc.CompareAndSetPassword(ctx, userID, old, new) }