diff --git a/contrib/gocbcorex b/contrib/gocbcorex index 1765a3cc..a4534856 160000 --- a/contrib/gocbcorex +++ b/contrib/gocbcorex @@ -1 +1 @@ -Subproject commit 1765a3cc6e2f2b2ef030ff6f13be928e5052ad02 +Subproject commit a4534856a17bdd7371d2ff91638666fd0a9d15e8 diff --git a/contrib/goprotostellar/go.mod b/contrib/goprotostellar/go.mod index d29714ab..b7c81d8b 100644 --- a/contrib/goprotostellar/go.mod +++ b/contrib/goprotostellar/go.mod @@ -1,16 +1,16 @@ module github.com/couchbase/goprotostellar -go 1.19 +go 1.18 require ( - google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 - google.golang.org/grpc v1.52.3 - google.golang.org/protobuf v1.28.1 + google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 + google.golang.org/grpc v1.53.0 + google.golang.org/protobuf v1.30.0 ) require ( github.com/golang/protobuf v1.5.2 // indirect - golang.org/x/net v0.4.0 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect ) diff --git a/contrib/goprotostellar/go.sum b/contrib/goprotostellar/go.sum index 764d9c71..b4eb1850 100644 --- a/contrib/goprotostellar/go.sum +++ b/contrib/goprotostellar/go.sum @@ -3,18 +3,18 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 h1:p0kMzw6AG0JEzd7Z+kXqOiLhC6gjUQTbtS2zR0Q3DbI= -google.golang.org/genproto v0.0.0-20230131230820-1c016267d619/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/contrib/protostellar b/contrib/protostellar index 28d1e244..40b90822 160000 --- a/contrib/protostellar +++ b/contrib/protostellar @@ -1 +1 @@ -Subproject commit 28d1e244584516bf8331a84e85ff82a5756c62d0 +Subproject commit 40b90822b38c1a288dae95a5facf9da0a73cc980 diff --git a/gateway/dataimpl/server_v1/errorhandler.go b/gateway/dataimpl/server_v1/errorhandler.go index a3f49d94..73d25118 100644 --- a/gateway/dataimpl/server_v1/errorhandler.go +++ b/gateway/dataimpl/server_v1/errorhandler.go @@ -181,7 +181,7 @@ func (e ErrorHandler) NewCollectionExistsStatus(baseErr error, bucketName, scope return st } -func (e ErrorHandler) NewSearchIndexExistsStatus(baseErr error, indexName string) *status.Status { +func (e ErrorHandler) NewSearchIndexMissingStatus(baseErr error, indexName string) *status.Status { st := status.New(codes.NotFound, fmt.Sprintf("Search index '%s' not found.", indexName)) @@ -194,7 +194,7 @@ func (e ErrorHandler) NewSearchIndexExistsStatus(baseErr error, indexName string return st } -func (e ErrorHandler) NewSearchIndexMissingStatus(baseErr error, indexName string) *status.Status { +func (e ErrorHandler) NewSearchIndexExistsStatus(baseErr error, indexName string) *status.Status { st := status.New(codes.AlreadyExists, fmt.Sprintf("Search index '%s' already existed.", indexName)) diff --git a/gateway/dataimpl/server_v1/searchadminserver.go b/gateway/dataimpl/server_v1/searchadminserver.go index 26e23237..0ecfa447 100644 --- a/gateway/dataimpl/server_v1/searchadminserver.go +++ b/gateway/dataimpl/server_v1/searchadminserver.go @@ -2,8 +2,12 @@ package server_v1 import ( "context" + "encoding/json" "errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "go.uber.org/zap" "github.com/couchbase/gocbcorex/cbsearchx" @@ -31,7 +35,7 @@ func NewSearchIndexAdminServer( } } -func (s *SearchIndexAdminServer) UpsertIndex(ctx context.Context, in *admin_search_v1.UpsertIndexRequest) (*admin_search_v1.UpsertIndexResponse, error) { +func (s *SearchIndexAdminServer) CreateIndex(ctx context.Context, in *admin_search_v1.CreateIndexRequest) (*admin_search_v1.CreateIndexResponse, error) { agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) if errSt != nil { return nil, errSt.Err() @@ -42,10 +46,12 @@ func (s *SearchIndexAdminServer) UpsertIndex(ctx context.Context, in *admin_sear Type: in.Type, } + index.Params = make(map[string]json.RawMessage, len(in.Params)) for key, param := range in.Params { index.Params[key] = param } + index.PlanParams = make(map[string]json.RawMessage, len(in.PlanParams)) for key, param := range in.PlanParams { index.PlanParams[key] = param } @@ -58,6 +64,7 @@ func (s *SearchIndexAdminServer) UpsertIndex(ctx context.Context, in *admin_sear index.SourceName = in.GetSourceName() } + index.SourceParams = make(map[string]json.RawMessage, len(in.SourceParams)) for key, param := range in.SourceParams { index.SourceParams[key] = param } @@ -70,14 +77,20 @@ func (s *SearchIndexAdminServer) UpsertIndex(ctx context.Context, in *admin_sear index.SourceUUID = in.GetSourceUuid() } - if in.Uuid != nil { - index.UUID = in.GetUuid() - } - - err := agent.UpsertSearchIndex(ctx, &cbsearchx.UpsertIndexOptions{ + opts := &cbsearchx.UpsertIndexOptions{ OnBehalfOf: oboInfo, Index: index, - }) + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.UpsertSearchIndex(ctx, opts) if err != nil { if errors.Is(err, cbsearchx.ErrIndexExists) { return nil, s.errorHandler.NewSearchIndexExistsStatus(err, in.Name).Err() @@ -85,7 +98,84 @@ func (s *SearchIndexAdminServer) UpsertIndex(ctx context.Context, in *admin_sear return nil, s.errorHandler.NewGenericStatus(err).Err() } - return &admin_search_v1.UpsertIndexResponse{}, nil + return &admin_search_v1.CreateIndexResponse{}, nil +} + +func (s *SearchIndexAdminServer) UpdateIndex(ctx context.Context, in *admin_search_v1.UpdateIndexRequest) (*admin_search_v1.UpdateIndexResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + if in.Index == nil { + return nil, status.New(codes.InvalidArgument, "Update index request missing index.").Err() + } + + if in.Index.Uuid == "" { + return nil, status.New(codes.InvalidArgument, "Update index request missing uuid.").Err() + } + + index := cbsearchx.Index{ + Name: in.Index.Name, + Type: in.Index.Type, + UUID: in.Index.Uuid, + } + + if in.Index.Params != nil { + index.Params = make(map[string]json.RawMessage, len(in.Index.Params)) + for key, param := range in.Index.Params { + index.Params[key] = param + } + } + + if in.Index.PlanParams != nil { + index.PlanParams = make(map[string]json.RawMessage, len(in.Index.PlanParams)) + for key, param := range in.Index.PlanParams { + index.PlanParams[key] = param + } + } + + if in.Index.SourceName != nil { + index.SourceName = in.Index.GetSourceName() + } + + if in.Index.SourceParams != nil { + index.SourceParams = make(map[string]json.RawMessage, len(in.Index.SourceParams)) + for key, param := range in.Index.SourceParams { + index.SourceParams[key] = param + } + } + + if in.Index.SourceType != nil { + index.SourceType = in.Index.GetSourceType() + } + + if in.Index.SourceUuid != nil { + index.SourceUUID = in.Index.GetSourceUuid() + } + + opts := &cbsearchx.UpsertIndexOptions{ + OnBehalfOf: oboInfo, + Index: index, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.UpsertSearchIndex(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Index.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.UpdateIndexResponse{}, nil } func (s *SearchIndexAdminServer) DeleteIndex(ctx context.Context, in *admin_search_v1.DeleteIndexRequest) (*admin_search_v1.DeleteIndexResponse, error) { @@ -94,12 +184,22 @@ func (s *SearchIndexAdminServer) DeleteIndex(ctx context.Context, in *admin_sear return nil, errSt.Err() } - err := agent.DeleteSearchIndex(ctx, &cbsearchx.DeleteIndexOptions{ + opts := &cbsearchx.DeleteIndexOptions{ IndexName: in.Name, OnBehalfOf: oboInfo, - }) + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.DeleteSearchIndex(ctx, opts) if err != nil { - if errors.Is(err, cbsearchx.ErrIndexExists) { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() } return nil, s.errorHandler.NewGenericStatus(err).Err() @@ -107,3 +207,360 @@ func (s *SearchIndexAdminServer) DeleteIndex(ctx context.Context, in *admin_sear return &admin_search_v1.DeleteIndexResponse{}, nil } + +func (s *SearchIndexAdminServer) GetIndex(ctx context.Context, in *admin_search_v1.GetIndexRequest) (*admin_search_v1.GetIndexResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.GetIndexOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + index, err := agent.GetSearchIndex(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + adminIndex := cbsearchxIndexToPS(index) + + return &admin_search_v1.GetIndexResponse{ + Index: adminIndex, + }, nil +} + +func (s *SearchIndexAdminServer) ListIndexes(ctx context.Context, in *admin_search_v1.ListIndexesRequest) (*admin_search_v1.ListIndexesResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.GetAllIndexesOptions{ + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + indexes, err := agent.GetAllSearchIndexes(ctx, opts) + if err != nil { + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + adminIndexes := make([]*admin_search_v1.Index, len(indexes)) + for i, index := range indexes { + adminIndex := cbsearchxIndexToPS(&index) + adminIndexes[i] = adminIndex + } + + return &admin_search_v1.ListIndexesResponse{ + Indexes: adminIndexes, + }, nil +} + +func (s *SearchIndexAdminServer) AnalyzeDocument(ctx context.Context, in *admin_search_v1.AnalyzeDocumentRequest) (*admin_search_v1.AnalyzeDocumentResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.AnalyzeDocumentOptions{ + IndexName: in.Name, + DocContent: in.Doc, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + analysis, err := agent.AnalyzeDocument(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.AnalyzeDocumentResponse{ + Status: analysis.Status, + Analyzed: analysis.Analyzed, + }, nil +} + +func (s *SearchIndexAdminServer) GetIndexedDocumentsCount(ctx context.Context, in *admin_search_v1.GetIndexedDocumentsCountRequest) (*admin_search_v1.GetIndexedDocumentsCountResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.GetIndexedDocumentsCountOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + count, err := agent.GetSearchIndexedDocumentsCount(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.GetIndexedDocumentsCountResponse{ + Count: count, + }, nil +} + +func (s *SearchIndexAdminServer) PauseIndexIngest(ctx context.Context, in *admin_search_v1.PauseIndexIngestRequest) (*admin_search_v1.PauseIndexIngestResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.PauseIngestOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.PauseSearchIndexIngest(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.PauseIndexIngestResponse{}, nil +} + +func (s *SearchIndexAdminServer) ResumeIndexIngest(ctx context.Context, in *admin_search_v1.ResumeIndexIngestRequest) (*admin_search_v1.ResumeIndexIngestResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.ResumeIngestOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.ResumeSearchIndexIngest(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.ResumeIndexIngestResponse{}, nil +} + +func (s *SearchIndexAdminServer) AllowIndexQuerying(ctx context.Context, in *admin_search_v1.AllowIndexQueryingRequest) (*admin_search_v1.AllowIndexQueryingResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.AllowQueryingOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.AllowSearchIndexQuerying(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.AllowIndexQueryingResponse{}, nil +} + +func (s *SearchIndexAdminServer) DisallowIndexQuerying(ctx context.Context, in *admin_search_v1.DisallowIndexQueryingRequest) (*admin_search_v1.DisallowIndexQueryingResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.DisallowQueryingOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.DisallowSearchIndexQuerying(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.DisallowIndexQueryingResponse{}, nil +} + +func (s *SearchIndexAdminServer) FreezeIndexPlan(ctx context.Context, in *admin_search_v1.FreezeIndexPlanRequest) (*admin_search_v1.FreezeIndexPlanResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.FreezePlanOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.FreezeSearchIndexPlan(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.FreezeIndexPlanResponse{}, nil +} + +func (s *SearchIndexAdminServer) UnfreezeIndexPlan(ctx context.Context, in *admin_search_v1.UnfreezeIndexPlanRequest) (*admin_search_v1.UnfreezeIndexPlanResponse, error) { + agent, oboInfo, errSt := s.authHandler.GetHttpOboAgent(ctx, in.BucketName) + if errSt != nil { + return nil, errSt.Err() + } + + opts := &cbsearchx.UnfreezePlanOptions{ + IndexName: in.Name, + OnBehalfOf: oboInfo, + } + + if in.BucketName != nil { + opts.BucketName = in.GetBucketName() + } + + if in.ScopeName != nil { + opts.ScopeName = in.GetScopeName() + } + + err := agent.UnfreezeSearchIndexPlan(ctx, opts) + if err != nil { + if errors.Is(err, cbsearchx.ErrIndexNotFound) { + return nil, s.errorHandler.NewSearchIndexMissingStatus(err, in.Name).Err() + } + return nil, s.errorHandler.NewGenericStatus(err).Err() + } + + return &admin_search_v1.UnfreezeIndexPlanResponse{}, nil +} + +func cbsearchxIndexToPS(index *cbsearchx.Index) *admin_search_v1.Index { + adminIndex := &admin_search_v1.Index{ + Name: index.Name, + Type: index.Type, + Uuid: index.UUID, + } + + if index.SourceName != "" { + adminIndex.SourceName = &index.SourceName + } + + if index.SourceType != "" { + adminIndex.SourceType = &index.SourceType + } + + if index.SourceUUID != "" { + adminIndex.SourceUuid = &index.SourceUUID + } + + if len(index.SourceParams) > 0 { + adminIndex.SourceParams = make(map[string][]byte, len(index.SourceParams)) + for name, param := range index.SourceParams { + adminIndex.SourceParams[name] = param + } + } + + if len(index.Params) > 0 { + adminIndex.Params = make(map[string][]byte, len(index.Params)) + for name, param := range index.Params { + adminIndex.Params[name] = param + } + } + + if len(index.PlanParams) > 0 { + adminIndex.PlanParams = make(map[string][]byte, len(index.PlanParams)) + for name, param := range index.PlanParams { + adminIndex.PlanParams[name] = param + } + } + + return adminIndex +} diff --git a/gateway/test/feature_test.go b/gateway/test/feature_test.go index ced5cb6e..e410d92b 100644 --- a/gateway/test/feature_test.go +++ b/gateway/test/feature_test.go @@ -5,10 +5,12 @@ const DefaultClusterVer = "7.2.1" type TestFeatureCode string var ( - TestFeatureKV = TestFeatureCode("kv") - TestFeatureSearch = TestFeatureCode("search") - TestFeatureQuery = TestFeatureCode("query") - TestFeatureQueryManagement = TestFeatureCode("querymgmt") + TestFeatureKV = TestFeatureCode("kv") + TestFeatureSearch = TestFeatureCode("search") + TestFeatureQuery = TestFeatureCode("query") + TestFeatureQueryManagement = TestFeatureCode("querymgmt") + TestFeatureSearchManagement = TestFeatureCode("searchmgmt") + TestFeatureSearchManagementCollections = TestFeatureCode("searchmgmtcollections") ) type TestFeature struct { @@ -18,6 +20,7 @@ type TestFeature struct { var ( SrvVer721 = NodeVersion{7, 2, 1, 0, 0, ""} + SrvVer750 = NodeVersion{7, 5, 0, 0, 0, ""} ) func (s *GatewayOpsTestSuite) SupportsFeature(code TestFeatureCode) bool { @@ -46,6 +49,10 @@ func (s *GatewayOpsTestSuite) SupportsFeature(code TestFeatureCode) bool { return true case TestFeatureQueryManagement: return true + case TestFeatureSearchManagement: + return true + case TestFeatureSearchManagementCollections: + return !s.clusterVersion.Lower(SrvVer750) } panic("found unsupported feature code") diff --git a/gateway/test/search_mgmt_test.go b/gateway/test/search_mgmt_test.go new file mode 100644 index 00000000..cf155060 --- /dev/null +++ b/gateway/test/search_mgmt_test.go @@ -0,0 +1,257 @@ +package test + +import ( + "context" + "encoding/json" + "time" + + "github.com/couchbase/goprotostellar/genproto/admin_search_v1" + "github.com/google/uuid" + "google.golang.org/grpc" +) + +func newIndexName() string { + indexName := "a" + uuid.New().String() + return indexName +} + +func (s *GatewayOpsTestSuite) TestCreateUpdateGetDeleteIndex() { + if !s.SupportsFeature(TestFeatureSearchManagement) { + s.T().Skip() + } + searchAdminClient := admin_search_v1.NewSearchAdminServiceClient(s.gatewayConn) + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + indexName := newIndexName() + + var bucket, scope *string + if s.scopeName != "" { + if !s.SupportsFeature(TestFeatureSearchManagementCollections) { + s.T().Skip() + } + bucket = &s.bucketName + scope = &s.scopeName + } + + sourceType := "couchbase" + resp, err := searchAdminClient.CreateIndex(ctx, &admin_search_v1.CreateIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + Type: "fulltext-index", + SourceType: &sourceType, + SourceName: &s.bucketName, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), resp, err) + + index, err := searchAdminClient.GetIndex(ctx, &admin_search_v1.GetIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), index, err) + + s.Assert().Equal(indexName, index.Index.Name) + s.Assert().Equal("fulltext-index", index.Index.Type) + + indexes, err := searchAdminClient.ListIndexes(ctx, &admin_search_v1.ListIndexesRequest{ + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), indexes, err) + + var found bool + for _, i := range indexes.Indexes { + if i.Name == indexName { + found = true + break + } + } + s.Assert().True(found, "Did not find expected index in GetAllIndexes") + + updateIndex := index.Index + planParams := map[string]interface{}{ + "test": map[string]interface{}{}, + "test2": map[string]interface{}{}, + } + b, err := json.Marshal(planParams) + s.Require().NoError(err) + + updateIndex.PlanParams = map[string][]byte{ + "targets": b, + } + updateResp, err := searchAdminClient.UpdateIndex(ctx, &admin_search_v1.UpdateIndexRequest{ + Index: updateIndex, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), updateResp, err) + + delResp, err := searchAdminClient.DeleteIndex(ctx, &admin_search_v1.DeleteIndexRequest{ + Name: updateIndex.Name, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), delResp, err) +} + +func (s *GatewayOpsTestSuite) TestIndexesIngestControl() { + if !s.SupportsFeature(TestFeatureSearchManagement) { + s.T().Skip() + } + searchAdminClient := admin_search_v1.NewSearchAdminServiceClient(s.gatewayConn) + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + indexName := newIndexName() + + var bucket, scope *string + if s.scopeName != "" { + if !s.SupportsFeature(TestFeatureSearchManagementCollections) { + s.T().Skip() + } + bucket = &s.bucketName + scope = &s.scopeName + } + + sourceType := "couchbase" + resp, err := searchAdminClient.CreateIndex(ctx, &admin_search_v1.CreateIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + Type: "fulltext-index", + SourceType: &sourceType, + SourceName: &s.bucketName, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), resp, err) + defer func() { + _, _ = searchAdminClient.DeleteIndex(ctx, &admin_search_v1.DeleteIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + }() + + pauseResp, err := searchAdminClient.PauseIndexIngest(ctx, &admin_search_v1.PauseIndexIngestRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), pauseResp, err) + + resumeResp, err := searchAdminClient.ResumeIndexIngest(ctx, &admin_search_v1.ResumeIndexIngestRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), resumeResp, err) +} + +func (s *GatewayOpsTestSuite) TestIndexesQueryControl() { + if !s.SupportsFeature(TestFeatureSearchManagement) { + s.T().Skip() + } + searchAdminClient := admin_search_v1.NewSearchAdminServiceClient(s.gatewayConn) + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + indexName := newIndexName() + + var bucket, scope *string + if s.scopeName != "" { + if !s.SupportsFeature(TestFeatureSearchManagementCollections) { + s.T().Skip() + } + bucket = &s.bucketName + scope = &s.scopeName + } + + sourceType := "couchbase" + resp, err := searchAdminClient.CreateIndex(ctx, &admin_search_v1.CreateIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + Type: "fulltext-index", + SourceType: &sourceType, + SourceName: &s.bucketName, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), resp, err) + defer func() { + _, _ = searchAdminClient.DeleteIndex(ctx, &admin_search_v1.DeleteIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + }() + + disallowResp, err := searchAdminClient.DisallowIndexQuerying(ctx, &admin_search_v1.DisallowIndexQueryingRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), disallowResp, err) + + allowResp, err := searchAdminClient.AllowIndexQuerying(ctx, &admin_search_v1.AllowIndexQueryingRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), allowResp, err) +} + +func (s *GatewayOpsTestSuite) TestIndexesPartitionControl() { + if !s.SupportsFeature(TestFeatureSearchManagement) { + s.T().Skip() + } + searchAdminClient := admin_search_v1.NewSearchAdminServiceClient(s.gatewayConn) + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + indexName := newIndexName() + + var bucket, scope *string + if s.scopeName != "" { + if !s.SupportsFeature(TestFeatureSearchManagementCollections) { + s.T().Skip() + } + bucket = &s.bucketName + scope = &s.scopeName + } + + sourceType := "couchbase" + resp, err := searchAdminClient.CreateIndex(ctx, &admin_search_v1.CreateIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + Type: "fulltext-index", + SourceType: &sourceType, + SourceName: &s.bucketName, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), resp, err) + defer func() { + _, _ = searchAdminClient.DeleteIndex(ctx, &admin_search_v1.DeleteIndexRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + }() + + freezeResp, err := searchAdminClient.FreezeIndexPlan(ctx, &admin_search_v1.FreezeIndexPlanRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), freezeResp, err) + + unfreezeResp, err := searchAdminClient.UnfreezeIndexPlan(ctx, &admin_search_v1.UnfreezeIndexPlanRequest{ + Name: indexName, + BucketName: bucket, + ScopeName: scope, + }, grpc.PerRPCCredentials(s.basicRpcCreds)) + requireRpcSuccess(s.T(), unfreezeResp, err) +} diff --git a/gateway/test/search_test.go b/gateway/test/search_test.go index 9074b33e..3e9c9a2d 100644 --- a/gateway/test/search_test.go +++ b/gateway/test/search_test.go @@ -235,7 +235,7 @@ func (s *testSearchServiceHelper) testSetupSearch() { s.Dataset = dataset sourceType := "couchbase" - resp, err := s.IndexClient.UpsertIndex(context.Background(), &admin_search_v1.UpsertIndexRequest{ + resp, err := s.IndexClient.CreateIndex(context.Background(), &admin_search_v1.CreateIndexRequest{ Name: s.IndexName, SourceName: &s.bucketName, SourceType: &sourceType, diff --git a/go.mod b/go.mod index c087bb3e..ade521af 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( go.opentelemetry.io/otel/trace v1.16.0 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 + google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 google.golang.org/grpc v1.55.0 google.golang.org/protobuf v1.30.0 ) @@ -47,6 +47,7 @@ require ( github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.2.1 // indirect github.com/couchbase/goutils v0.1.2 // indirect + github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/go.sum b/go.sum index 6d79ed40..351771ca 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,8 @@ github.com/couchbaselabs/gocaves/client v0.0.0-20220223122017-22859b310bd2 h1:Ul github.com/couchbaselabs/gocaves/client v0.0.0-20220223122017-22859b310bd2/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY= github.com/couchbaselabs/gocbconnstr v1.0.5 h1:e0JokB5qbcz7rfnxEhNRTKz8q1svoRvDoZihsiwNigA= github.com/couchbaselabs/gocbconnstr v1.0.5/go.mod h1:KV3fnIKMi8/AzX0O9zOrO9rofEqrRF1d2rG7qqjxC7o= +github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131 h1:2EAfFswAfgYn3a05DVcegiw6DgMgn1Mv5eGz6IHt1Cw= +github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -665,8 +667,8 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=