diff --git a/extensions/search/pkg/search/index/index.go b/extensions/search/pkg/search/index/index.go index 0c2ffecfd..bc06ba466 100644 --- a/extensions/search/pkg/search/index/index.go +++ b/extensions/search/pkg/search/index/index.go @@ -26,7 +26,10 @@ import ( "time" "github.com/blevesearch/bleve/v2" + "github.com/blevesearch/bleve/v2/analysis/analyzer/custom" "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword" + "github.com/blevesearch/bleve/v2/analysis/token/lowercase" + "github.com/blevesearch/bleve/v2/analysis/tokenizer/single" "github.com/blevesearch/bleve/v2/mapping" "google.golang.org/protobuf/types/known/timestamppb" @@ -57,7 +60,11 @@ type Index struct { // NewPersisted returns a new instance of Index with the data being persisted in the given directory func NewPersisted(path string) (*Index, error) { - bi, err := bleve.New(path, BuildMapping()) + mapping, err := BuildMapping() + if err != nil { + return nil, err + } + bi, err := bleve.New(path, mapping) if err != nil { return nil, err } @@ -157,14 +164,14 @@ func (i *Index) Purge(id *sprovider.ResourceId) error { return i.bleveIndex.Delete(idToBleveId(id)) } -// Purge removes an entity from the index -func (i *Index) Move(ri *sprovider.ResourceInfo) error { +// Move update the path of an entry and all its children +func (i *Index) Move(ri *sprovider.ResourceInfo, fullPath string) error { doc, err := i.getEntity(idToBleveId(ri.Id)) if err != nil { return err } oldName := doc.Path - newName := utils.MakeRelativePath(ri.Path) + newName := utils.MakeRelativePath(fullPath) doc, err = i.updateEntity(idToBleveId(ri.Id), func(doc *indexDocument) { doc.Path = newName @@ -204,7 +211,7 @@ func (i *Index) Search(ctx context.Context, req *searchsvc.SearchIndexRequest) ( deletedQuery := bleve.NewBoolFieldQuery(false) deletedQuery.SetField("Deleted") query := bleve.NewConjunctionQuery( - bleve.NewQueryStringQuery("Name:"+req.Query), + bleve.NewQueryStringQuery("Name:"+strings.ToLower(req.Query)), deletedQuery, // Skip documents that have been marked as deleted bleve.NewQueryStringQuery("RootID:"+req.Ref.ResourceId.StorageId+"!"+req.Ref.ResourceId.OpaqueId), // Limit search to the space bleve.NewQueryStringQuery("Path:"+utils.MakeRelativePath(path.Join(req.Ref.Path, "/"))+"*"), // Limit search to this directory in the space @@ -232,10 +239,29 @@ func (i *Index) Search(ctx context.Context, req *searchsvc.SearchIndexRequest) ( } // BuildMapping builds a bleve index mapping which can be used for indexing -func BuildMapping() mapping.IndexMapping { +func BuildMapping() (mapping.IndexMapping, error) { + nameMapping := bleve.NewTextFieldMapping() + nameMapping.Analyzer = "lowercaseKeyword" + + docMapping := bleve.NewDocumentMapping() + docMapping.AddFieldMappingsAt("Name", nameMapping) + indexMapping := bleve.NewIndexMapping() indexMapping.DefaultAnalyzer = keyword.Name - return indexMapping + indexMapping.DefaultMapping = docMapping + err := indexMapping.AddCustomAnalyzer("lowercaseKeyword", + map[string]interface{}{ + "type": custom.Name, + "tokenizer": single.Name, + "token_filters": []string{ + lowercase.Name, + }, + }) + if err != nil { + return nil, err + } + + return indexMapping, nil } func toEntity(ref *sprovider.Reference, ri *sprovider.ResourceInfo) *indexDocument { diff --git a/extensions/search/pkg/search/index/index_test.go b/extensions/search/pkg/search/index/index_test.go index 69f1a17e7..7c46c64b2 100644 --- a/extensions/search/pkg/search/index/index_test.go +++ b/extensions/search/pkg/search/index/index_test.go @@ -37,7 +37,7 @@ var _ = Describe("Index", func() { StorageId: "storageid", OpaqueId: "someopaqueid", }, - Path: "foo.pdf", + Path: "Foo.pdf", Size: 12345, Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, MimeType: "application/pdf", @@ -45,7 +45,7 @@ var _ = Describe("Index", func() { } parentRef = &sprovider.Reference{ ResourceId: rootId, - Path: "./sudbir", + Path: "./my/sudbir", } parentRi = &sprovider.ResourceInfo{ Id: &sprovider.ResourceId{ @@ -59,7 +59,7 @@ var _ = Describe("Index", func() { } childRef = &sprovider.Reference{ ResourceId: rootId, - Path: "./sudbir/child.pdf", + Path: "./my/sudbir/child.pdf", } childRi = &sprovider.ResourceInfo{ Id: &sprovider.ResourceId{ @@ -92,8 +92,10 @@ var _ = Describe("Index", func() { ) BeforeEach(func() { - var err error - bleveIndex, err = bleve.NewMemOnly(index.BuildMapping()) + mapping, err := index.BuildMapping() + Expect(err).ToNot(HaveOccurred()) + + bleveIndex, err = bleve.NewMemOnly(mapping) Expect(err).ToNot(HaveOccurred()) i, err = index.New(bleveIndex) @@ -201,6 +203,21 @@ var _ = Describe("Index", func() { } }) + It("is case-insensitive", func() { + res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{ + Ref: &searchmsg.Reference{ + ResourceId: &searchmsg.ResourceID{ + StorageId: ref.ResourceId.StorageId, + OpaqueId: ref.ResourceId.OpaqueId, + }, + }, + Query: "Foo*", + }) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(len(res.Matches)).To(Equal(1)) + }) + Context("and an additional file in a subdirectory", func() { var ( nestedRef *sprovider.Reference @@ -349,7 +366,7 @@ var _ = Describe("Index", func() { Expect(err).ToNot(HaveOccurred()) parentRi.Path = "newname" - err = i.Move(parentRi) + err = i.Move(parentRi, "./somewhere/else/newname") Expect(err).ToNot(HaveOccurred()) assertDocCount(rootId, "subdir", 0) @@ -365,7 +382,7 @@ var _ = Describe("Index", func() { }) Expect(err).ToNot(HaveOccurred()) Expect(len(res.Matches)).To(Equal(1)) - Expect(res.Matches[0].Entity.Ref.Path).To(Equal("./newname/child.pdf")) + Expect(res.Matches[0].Entity.Ref.Path).To(Equal("./somewhere/else/newname/child.pdf")) }) }) }) diff --git a/extensions/search/pkg/search/mocks/IndexClient.go b/extensions/search/pkg/search/mocks/IndexClient.go index ea79cd506..f43f9f3a9 100644 --- a/extensions/search/pkg/search/mocks/IndexClient.go +++ b/extensions/search/pkg/search/mocks/IndexClient.go @@ -65,13 +65,13 @@ func (_m *IndexClient) DocCount() (uint64, error) { return r0, r1 } -// Move provides a mock function with given fields: ri -func (_m *IndexClient) Move(ri *providerv1beta1.ResourceInfo) error { - ret := _m.Called(ri) +// Move provides a mock function with given fields: ri, path +func (_m *IndexClient) Move(ri *providerv1beta1.ResourceInfo, path string) error { + ret := _m.Called(ri, path) var r0 error - if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceInfo) error); ok { - r0 = rf(ri) + if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceInfo, string) error); ok { + r0 = rf(ri, path) } else { r0 = ret.Error(0) } diff --git a/extensions/search/pkg/search/provider/events.go b/extensions/search/pkg/search/provider/events.go index 906067607..d243c5237 100644 --- a/extensions/search/pkg/search/provider/events.go +++ b/extensions/search/pkg/search/provider/events.go @@ -6,8 +6,10 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/events" "google.golang.org/grpc/metadata" ) @@ -59,17 +61,25 @@ func (p *Provider) handleEvent(ev interface{}) { p.logger.Error().Err(err).Msg("failed to stat the changed resource") return } - - switch statRes.Status.Code { - case rpc.Code_CODE_OK: - err = p.indexClient.Move(statRes.Info) - if err != nil { - p.logger.Error().Err(err).Msg("failed to restore the changed resource in the index") - } - default: + if statRes.Status.Code != rpc.Code_CODE_OK { p.logger.Error().Interface("statRes", statRes).Msg("failed to stat the changed resource") + return } + gpRes, err := p.getPath(statRes.Info.Id, owner) + if err != nil { + p.logger.Error().Err(err).Interface("ref", ref).Msg("failed to get path for moved resource") + return + } + if gpRes.Status.Code != rpcv1beta1.Code_CODE_OK { + p.logger.Error().Interface("status", gpRes.Status).Interface("ref", ref).Msg("failed to get path for moved resource") + return + } + + err = p.indexClient.Move(statRes.Info, gpRes.Path) + if err != nil { + p.logger.Error().Err(err).Msg("failed to restore the changed resource in the index") + } return case events.ContainerCreated: ref = e.Ref @@ -111,18 +121,38 @@ func (p *Provider) handleEvent(ev interface{}) { } func (p *Provider) statResource(ref *provider.Reference, owner *user.User) (*provider.StatResponse, error) { - // Get auth + ownerCtx, err := p.getAuthContext(owner) + if err != nil { + return nil, err + } + + // Stat changed resource resource + return p.gwClient.Stat(ownerCtx, &provider.StatRequest{Ref: ref}) +} + +func (p *Provider) getPath(id *provider.ResourceId, owner *user.User) (*provider.GetPathResponse, error) { + ownerCtx, err := p.getAuthContext(owner) + if err != nil { + return nil, err + } + + // Stat changed resource resource + return p.gwClient.GetPath(ownerCtx, &provider.GetPathRequest{ResourceId: id}) +} + +func (p *Provider) getAuthContext(owner *user.User) (context.Context, error) { ownerCtx := ctxpkg.ContextSetUser(context.Background(), owner) authRes, err := p.gwClient.Authenticate(ownerCtx, &gateway.AuthenticateRequest{ Type: "machine", ClientId: "userid:" + owner.Id.OpaqueId, ClientSecret: p.machineAuthAPIKey, }) - if err != nil || authRes.GetStatus().GetCode() != rpc.Code_CODE_OK { - p.logger.Error().Err(err).Interface("authRes", authRes).Msg("error using machine auth") + if err == nil && authRes.GetStatus().GetCode() != rpc.Code_CODE_OK { + err = errtypes.NewErrtypeFromStatus(authRes.Status) } - ownerCtx = metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token) - - // Stat changed resource resource - return p.gwClient.Stat(ownerCtx, &provider.StatRequest{Ref: ref}) + if err != nil { + p.logger.Error().Err(err).Interface("authRes", authRes).Msg("error using machine auth") + return nil, err + } + return metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token), nil } diff --git a/extensions/search/pkg/search/provider/events_test.go b/extensions/search/pkg/search/provider/events_test.go new file mode 100644 index 000000000..9de17c5df --- /dev/null +++ b/extensions/search/pkg/search/provider/events_test.go @@ -0,0 +1,175 @@ +package provider_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/status" + cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" + "github.com/owncloud/ocis/v2/extensions/search/pkg/search/mocks" + provider "github.com/owncloud/ocis/v2/extensions/search/pkg/search/provider" + "github.com/owncloud/ocis/v2/ocis-pkg/log" +) + +var _ = Describe("Searchprovider", func() { + var ( + p *provider.Provider + gwClient *cs3mocks.GatewayAPIClient + indexClient *mocks.IndexClient + + ctx context.Context + eventsChan chan interface{} + + logger = log.NewLogger() + user = &userv1beta1.User{ + Id: &userv1beta1.UserId{ + OpaqueId: "user", + }, + } + + ref = &sprovider.Reference{ + ResourceId: &sprovider.ResourceId{ + StorageId: "storageid", + OpaqueId: "rootopaqueid", + }, + Path: "./foo.pdf", + } + ri = &sprovider.ResourceInfo{ + Id: &sprovider.ResourceId{ + StorageId: "storageid", + OpaqueId: "opaqueid", + }, + Path: "foo.pdf", + Size: 12345, + } + ) + + BeforeEach(func() { + ctx = context.Background() + eventsChan = make(chan interface{}) + gwClient = &cs3mocks.GatewayAPIClient{} + indexClient = &mocks.IndexClient{} + + p = provider.New(gwClient, indexClient, "", eventsChan, logger) + + gwClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{ + Status: status.NewOK(ctx), + Token: "authtoken", + }, nil) + gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ + Status: status.NewOK(context.Background()), + Info: ri, + }, nil) + indexClient.On("DocCount").Return(uint64(1), nil) + }) + + Describe("New", func() { + It("returns a new instance", func() { + p = provider.New(gwClient, indexClient, "", eventsChan, logger) + Expect(p).ToNot(BeNil()) + }) + }) + + Describe("events", func() { + It("triggers an index update when a file has been uploaded", func() { + called := false + indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool { + return riToIndex.Id.OpaqueId == ri.Id.OpaqueId + })).Return(nil).Run(func(args mock.Arguments) { + called = true + }) + eventsChan <- events.FileUploaded{ + Ref: ref, + Executant: user.Id, + } + + Eventually(func() bool { + return called + }, "2s").Should(BeTrue()) + }) + + It("removes an entry from the index when the file has been deleted", func() { + called := false + gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ + Status: status.NewNotFound(context.Background(), ""), + }, nil) + indexClient.On("Delete", mock.MatchedBy(func(id *sprovider.ResourceId) bool { + return id.OpaqueId == ri.Id.OpaqueId + })).Return(nil).Run(func(args mock.Arguments) { + called = true + }) + eventsChan <- events.ItemTrashed{ + Ref: ref, + ID: ri.Id, + Executant: user.Id, + } + + Eventually(func() bool { + return called + }, "2s").Should(BeTrue()) + }) + + It("indexes items when they are being restored", func() { + called := false + indexClient.On("Restore", mock.MatchedBy(func(id *sprovider.ResourceId) bool { + return id.OpaqueId == ri.Id.OpaqueId + })).Return(nil).Run(func(args mock.Arguments) { + called = true + }) + eventsChan <- events.ItemRestored{ + Ref: ref, + Executant: user.Id, + } + + Eventually(func() bool { + return called + }, "2s").Should(BeTrue()) + }) + + It("indexes items when a version has been restored", func() { + called := false + indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool { + return riToIndex.Id.OpaqueId == ri.Id.OpaqueId + })).Return(nil).Run(func(args mock.Arguments) { + called = true + }) + eventsChan <- events.FileVersionRestored{ + Ref: ref, + Executant: user.Id, + } + + Eventually(func() bool { + return called + }, "2s").Should(BeTrue()) + }) + + It("indexes items when they are being moved", func() { + called := false + gwClient.On("GetPath", mock.Anything, mock.Anything).Return(&sprovider.GetPathResponse{ + Status: status.NewOK(ctx), + Path: "./new/path.pdf", + }, nil) + indexClient.On("Move", mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool { + return riToIndex.Id.OpaqueId == ri.Id.OpaqueId + }), "./new/path.pdf").Return(nil).Run(func(args mock.Arguments) { + called = true + }) + ref.Path = "./new/path.pdf" + eventsChan <- events.ItemMoved{ + Ref: ref, + Executant: user.Id, + } + + Eventually(func() bool { + return called + }, "2s").Should(BeTrue()) + }) + }) +}) diff --git a/extensions/search/pkg/search/provider/searchprovider.go b/extensions/search/pkg/search/provider/searchprovider.go index 9356af88a..940405635 100644 --- a/extensions/search/pkg/search/provider/searchprovider.go +++ b/extensions/search/pkg/search/provider/searchprovider.go @@ -14,6 +14,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" + sdk "github.com/cs3org/reva/v2/pkg/sdk/common" "github.com/cs3org/reva/v2/pkg/storage/utils/walker" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" @@ -57,6 +58,7 @@ func (p *Provider) Search(ctx context.Context, req *searchsvc.SearchRequest) (*s if req.Query == "" { return nil, errtypes.PreconditionFailed("empty query provided") } + p.logger.Debug().Str("query", req.Query).Msg("performing a search") listSpacesRes, err := p.gwClient.ListStorageSpaces(ctx, &provider.ListStorageSpacesRequest{ Filters: []*provider.ListStorageSpacesRequest_Filter{ @@ -67,27 +69,61 @@ func (p *Provider) Search(ctx context.Context, req *searchsvc.SearchRequest) (*s }, }) if err != nil { + p.logger.Error().Err(err).Msg("failed to list the user's storage spaces") return nil, err } + mountpointMap := map[string]string{} + for _, space := range listSpacesRes.StorageSpaces { + if space.SpaceType != "mountpoint" { + continue + } + opaqueMap := sdk.DecodeOpaqueMap(space.Opaque) + grantSpaceId := storagespace.FormatResourceID(provider.ResourceId{ + StorageId: opaqueMap["grantStorageID"], + OpaqueId: opaqueMap["grantOpaqueID"], + }) + mountpointMap[grantSpaceId] = space.Id.OpaqueId + } + matches := []*searchmsg.Match{} for _, space := range listSpacesRes.StorageSpaces { - pathPrefix := "" - if space.SpaceType == "grant" { + var mountpointRootId *searchmsg.ResourceID + mountpointPrefix := "" + switch space.SpaceType { + case "mountpoint": + continue // mountpoint spaces are only "links" to the shared spaces. we have to search the shared "grant" space instead + case "grant": + mountpointId, ok := mountpointMap[space.Id.OpaqueId] + if !ok { + p.logger.Warn().Interface("space", space).Msg("could not find mountpoint space for grant space") + continue + } gpRes, err := p.gwClient.GetPath(ctx, &provider.GetPathRequest{ ResourceId: space.Root, }) if err != nil { - return nil, err + p.logger.Error().Err(err).Str("space", space.Id.OpaqueId).Msg("failed to get path for grant space root") + continue } if gpRes.Status.Code != rpcv1beta1.Code_CODE_OK { - return nil, errtypes.NewErrtypeFromStatus(gpRes.Status) + p.logger.Error().Interface("status", gpRes.Status).Str("space", space.Id.OpaqueId).Msg("failed to get path for grant space root") + continue } - pathPrefix = utils.MakeRelativePath(gpRes.Path) + mountpointPrefix = utils.MakeRelativePath(gpRes.Path) + sid, oid, err := storagespace.SplitID(mountpointId) + if err != nil { + p.logger.Error().Err(err).Str("space", space.Id.OpaqueId).Str("mountpointId", mountpointId).Msg("invalid mountpoint space id") + continue + } + mountpointRootId = &searchmsg.ResourceID{ + StorageId: sid, + OpaqueId: oid, + } + p.logger.Debug().Interface("grantSpace", space).Interface("mountpointRootId", mountpointRootId).Msg("searching a grant") } _, rootStorageID := storagespace.SplitStorageID(space.Root.StorageId) - res, err := p.indexClient.Search(ctx, &searchsvc.SearchIndexRequest{ Query: req.Query, Ref: &searchmsg.Reference{ @@ -95,16 +131,21 @@ func (p *Provider) Search(ctx context.Context, req *searchsvc.SearchRequest) (*s StorageId: space.Root.StorageId, OpaqueId: rootStorageID, }, - Path: pathPrefix, + Path: mountpointPrefix, }, }) if err != nil { + p.logger.Error().Err(err).Str("space", space.Id.OpaqueId).Msg("failed to search the index") return nil, err } + p.logger.Debug().Str("space", space.Id.OpaqueId).Int("hits", len(res.Matches)).Msg("space search done") for _, match := range res.Matches { - if pathPrefix != "" { - match.Entity.Ref.Path = utils.MakeRelativePath(strings.TrimPrefix(match.Entity.Ref.Path, pathPrefix)) + if mountpointPrefix != "" { + match.Entity.Ref.Path = utils.MakeRelativePath(strings.TrimPrefix(match.Entity.Ref.Path, mountpointPrefix)) + } + if mountpointRootId != nil { + match.Entity.Ref.ResourceId = mountpointRootId } matches = append(matches, match) } diff --git a/extensions/search/pkg/search/provider/searchprovider_test.go b/extensions/search/pkg/search/provider/searchprovider_test.go index 956d60df0..37f59ff1c 100644 --- a/extensions/search/pkg/search/provider/searchprovider_test.go +++ b/extensions/search/pkg/search/provider/searchprovider_test.go @@ -11,7 +11,6 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/rgrpc/status" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" "github.com/owncloud/ocis/v2/extensions/search/pkg/search/mocks" @@ -55,13 +54,6 @@ var _ = Describe("Searchprovider", func() { Name: "personalspace", } - ref = &sprovider.Reference{ - ResourceId: &sprovider.ResourceId{ - StorageId: "storageid", - OpaqueId: "rootopaqueid", - }, - Path: "./foo.pdf", - } ri = &sprovider.ResourceInfo{ Id: &sprovider.ResourceId{ StorageId: "storageid", @@ -98,98 +90,6 @@ var _ = Describe("Searchprovider", func() { }) }) - Describe("events", func() { - It("trigger an index update when a file has been uploaded", func() { - called := false - indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool { - return riToIndex.Id.OpaqueId == ri.Id.OpaqueId - })).Return(nil).Run(func(args mock.Arguments) { - called = true - }) - eventsChan <- events.FileUploaded{ - Ref: ref, - Executant: user.Id, - } - - Eventually(func() bool { - return called - }, "2s").Should(BeTrue()) - }) - - It("removes an entry from the index when the file has been deleted", func() { - called := false - - gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ - Status: status.NewNotFound(context.Background(), ""), - }, nil) - indexClient.On("Delete", mock.MatchedBy(func(id *sprovider.ResourceId) bool { - return id.OpaqueId == ri.Id.OpaqueId - })).Return(nil).Run(func(args mock.Arguments) { - called = true - }) - eventsChan <- events.ItemTrashed{ - Ref: ref, - ID: ri.Id, - Executant: user.Id, - } - - Eventually(func() bool { - return called - }, "2s").Should(BeTrue()) - }) - - It("indexes items when they are being restored", func() { - called := false - indexClient.On("Restore", mock.MatchedBy(func(id *sprovider.ResourceId) bool { - return id.OpaqueId == ri.Id.OpaqueId - })).Return(nil).Run(func(args mock.Arguments) { - called = true - }) - eventsChan <- events.ItemRestored{ - Ref: ref, - Executant: user.Id, - } - - Eventually(func() bool { - return called - }, "2s").Should(BeTrue()) - }) - - It("indexes items when a version has been restored", func() { - called := false - indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool { - return riToIndex.Id.OpaqueId == ri.Id.OpaqueId - })).Return(nil).Run(func(args mock.Arguments) { - called = true - }) - eventsChan <- events.FileVersionRestored{ - Ref: ref, - Executant: user.Id, - } - - Eventually(func() bool { - return called - }, "2s").Should(BeTrue()) - }) - - It("indexes items when they are being moved", func() { - called := false - indexClient.On("Move", mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool { - return riToIndex.Id.OpaqueId == ri.Id.OpaqueId - })).Return(nil).Run(func(args mock.Arguments) { - called = true - }) - eventsChan <- events.ItemMoved{ - Ref: ref, - Executant: user.Id, - } - - Eventually(func() bool { - return called - }, "2s").Should(BeTrue()) - }) - }) - Describe("IndexSpace", func() { It("walks the space and indexes all files", func() { gwClient.On("GetUserByClaim", mock.Anything, mock.Anything).Return(&userv1beta1.GetUserByClaimResponse{ @@ -267,7 +167,8 @@ var _ = Describe("Searchprovider", func() { Context("with received shares", func() { var ( - grantSpace *sprovider.StorageSpace + grantSpace *sprovider.StorageSpace + mountpointSpace *sprovider.StorageSpace ) BeforeEach(func() { @@ -275,19 +176,32 @@ var _ = Describe("Searchprovider", func() { SpaceType: "grant", Owner: otherUser, Id: &sprovider.StorageSpaceId{OpaqueId: "otherspaceroot!otherspacegrant"}, - Root: &sprovider.ResourceId{StorageId: "otherspaceroot", OpaqueId: "otherspaceroot"}, + Root: &sprovider.ResourceId{StorageId: "otherspaceroot", OpaqueId: "otherspacegrant"}, Name: "grantspace", } + mountpointSpace = &sprovider.StorageSpace{ + SpaceType: "mountpoint", + Owner: otherUser, + Id: &sprovider.StorageSpaceId{OpaqueId: "otherspaceroot!otherspacemountpoint"}, + Root: &sprovider.ResourceId{StorageId: "otherspaceroot", OpaqueId: "otherspacemountpoint"}, + Name: "mountpointspace", + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "grantStorageID": {Decoder: "plain", Value: []byte("otherspaceroot")}, + "grantOpaqueID": {Decoder: "plain", Value: []byte("otherspacegrant")}, + }, + }, + } gwClient.On("GetPath", mock.Anything, mock.Anything).Return(&sprovider.GetPathResponse{ Status: status.NewOK(ctx), Path: "/grant/path", }, nil) }) - It("searches the received spaces (grants)", func() { + It("searches the received spaces", func() { gwClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), - StorageSpaces: []*sprovider.StorageSpace{grantSpace}, + StorageSpaces: []*sprovider.StorageSpace{grantSpace, mountpointSpace}, }, nil) indexClient.On("Search", mock.Anything, mock.Anything).Return(&searchsvc.SearchIndexResponse{ Matches: []*searchmsg.Match{ @@ -319,7 +233,7 @@ var _ = Describe("Searchprovider", func() { match := res.Matches[0] Expect(match.Entity.Id.OpaqueId).To(Equal("grant-shared-id")) Expect(match.Entity.Name).To(Equal("Shared.pdf")) - Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(grantSpace.Root.OpaqueId)) + Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(mountpointSpace.Root.OpaqueId)) Expect(match.Entity.Ref.Path).To(Equal("./to/Shared.pdf")) indexClient.AssertCalled(GinkgoT(), "Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool { @@ -330,7 +244,7 @@ var _ = Describe("Searchprovider", func() { It("finds matches in both the personal space AND the grant", func() { gwClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), - StorageSpaces: []*sprovider.StorageSpace{personalSpace, grantSpace}, + StorageSpaces: []*sprovider.StorageSpace{personalSpace, grantSpace, mountpointSpace}, }, nil) indexClient.On("Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool { return req.Ref.ResourceId.StorageId == grantSpace.Root.StorageId diff --git a/extensions/search/pkg/search/search.go b/extensions/search/pkg/search/search.go index 3e9867c06..12df20342 100644 --- a/extensions/search/pkg/search/search.go +++ b/extensions/search/pkg/search/search.go @@ -38,7 +38,7 @@ type ProviderClient interface { type IndexClient interface { Search(ctx context.Context, req *searchsvc.SearchIndexRequest) (*searchsvc.SearchIndexResponse, error) Add(ref *providerv1beta1.Reference, ri *providerv1beta1.ResourceInfo) error - Move(ri *providerv1beta1.ResourceInfo) error + Move(ri *providerv1beta1.ResourceInfo, path string) error Delete(ri *providerv1beta1.ResourceId) error Restore(ri *providerv1beta1.ResourceId) error Purge(ri *providerv1beta1.ResourceId) error diff --git a/extensions/search/pkg/service/v0/service.go b/extensions/search/pkg/service/v0/service.go index b2e4873b1..ef589ec5e 100644 --- a/extensions/search/pkg/service/v0/service.go +++ b/extensions/search/pkg/service/v0/service.go @@ -46,7 +46,11 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, error) { indexDir := filepath.Join(cfg.Datapath, "index.bleve") bleveIndex, err := bleve.Open(indexDir) if err != nil { - bleveIndex, err = bleve.New(indexDir, index.BuildMapping()) + mapping, err := index.BuildMapping() + if err != nil { + return nil, err + } + bleveIndex, err = bleve.New(indexDir, mapping) if err != nil { return nil, err }