mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-14 06:39:07 -04:00
fix(search): take a deleted space out of the index when it is deleted
This commit is contained in:
1 parent
5e55b80e2c
commit
45403e0909
10 files changed
+277
-3
No files matched your search
@@ -225,6 +225,31 @@ func (b *Backend) Purge(id string, onlyDeleted bool) error {
|
||||
return batch.Push()
|
||||
}
|
||||
|
||||
func (b *Backend) PurgeSpace(rootID string) error {
|
||||
for {
|
||||
req := bleve.NewSearchRequest(&query.TermQuery{FieldVal: "RootID", Term: rootID})
|
||||
req.Size = defaultBatchSize
|
||||
|
||||
res, err := b.index.Search(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.Hits.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
batch := b.index.NewBatch()
|
||||
for _, hit := range res.Hits {
|
||||
batch.Delete(hit.ID)
|
||||
}
|
||||
|
||||
if err := b.index.Batch(batch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) NewBatch(size int) (search.BatchOperator, error) {
|
||||
return NewBatch(b.index, size)
|
||||
}
|
||||
@@ -103,6 +103,52 @@ var _ = Describe("Bleve", func() {
|
||||
}
|
||||
})
|
||||
|
||||
Describe("PurgeSpace", func() {
|
||||
It("takes every record of that space out of the index", func() {
|
||||
otherSpace := search.Resource{
|
||||
ID: "1$9!9",
|
||||
RootID: "1$9!9",
|
||||
Path: ".",
|
||||
Document: content.Document{Name: "other"},
|
||||
}
|
||||
for _, resource := range []search.Resource{rootResource, parentResource, childResource, otherSpace} {
|
||||
Expect(eng.Upsert(resource.ID, resource)).To(Succeed())
|
||||
}
|
||||
|
||||
Expect(eng.PurgeSpace(rootResource.RootID)).To(Succeed())
|
||||
|
||||
count, err := idx.DocCount()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(Equal(uint64(1)), "only the records of that space are gone")
|
||||
})
|
||||
|
||||
It("takes a space out that holds more records than one round", func() {
|
||||
otherSpace := search.Resource{
|
||||
ID: "1$9!9",
|
||||
RootID: "1$9!9",
|
||||
Path: ".",
|
||||
Document: content.Document{Name: "other"},
|
||||
}
|
||||
Expect(eng.Upsert(otherSpace.ID, otherSpace)).To(Succeed())
|
||||
|
||||
for i := range 120 {
|
||||
resource := search.Resource{
|
||||
ID: fmt.Sprintf("%s!file-%d", rootResource.RootID, i),
|
||||
RootID: rootResource.RootID,
|
||||
Path: fmt.Sprintf("./file-%d", i),
|
||||
Document: content.Document{Name: fmt.Sprintf("file-%d", i)},
|
||||
}
|
||||
Expect(eng.Upsert(resource.ID, resource)).To(Succeed())
|
||||
}
|
||||
|
||||
Expect(eng.PurgeSpace(rootResource.RootID)).To(Succeed())
|
||||
|
||||
count, err := idx.DocCount()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(Equal(uint64(1)), "only the record of the other space is left")
|
||||
})
|
||||
})
|
||||
|
||||
Describe("New", func() {
|
||||
It("returns a new index instance", func() {
|
||||
b := bleve.NewBackend(idx, bleveQuery.DefaultCreator, log.Logger{})
|
||||
|
||||
@@ -246,6 +246,32 @@ func (b *Backend) Purge(id string, onlyDeleted bool) error {
|
||||
return batch.Push()
|
||||
}
|
||||
|
||||
func (b *Backend) PurgeSpace(rootID string) error {
|
||||
req, err := osu.BuildDocumentDeleteByQueryReq(
|
||||
opensearchgoAPI.DocumentDeleteByQueryReq{
|
||||
Indices: []string{b.index},
|
||||
Params: opensearchgoAPI.DocumentDeleteByQueryParams{
|
||||
WaitForCompletion: conversions.ToPointer(true),
|
||||
Refresh: conversions.ToPointer(true),
|
||||
},
|
||||
},
|
||||
osu.NewBoolQuery().Must(osu.NewTermQuery[string]("RootID").Value(rootID)),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build the space purge request %s: %w", rootID, err)
|
||||
}
|
||||
|
||||
resp, err := b.client.Document.DeleteByQuery(context.TODO(), req)
|
||||
switch {
|
||||
case err != nil:
|
||||
return fmt.Errorf("failed to purge space %s: %w", rootID, err)
|
||||
case len(resp.Failures) != 0:
|
||||
return fmt.Errorf("failed to purge space %s: %v", rootID, resp.Failures)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewBatch(size int) (search.BatchOperator, error) {
|
||||
return NewBatch(b.client, b.index, size)
|
||||
}
|
||||
@@ -355,6 +355,48 @@ var _ = Describe("Backend", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("PurgeSpace", func() {
|
||||
const indexName = "opencloud-test-engine-purge-space"
|
||||
|
||||
var (
|
||||
tc *opensearchtest.TestClient
|
||||
backend *opensearch.Backend
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
tc = opensearchtest.NewDefaultTestClient(GinkgoTB(), defaultConfig.Engine.OpenSearch.Client)
|
||||
tc.Require.IndicesReset([]string{indexName})
|
||||
tc.Require.IndicesCount([]string{indexName}, nil, 0)
|
||||
deleteIndexOnCleanup(tc, indexName)
|
||||
|
||||
var err error
|
||||
backend, err = opensearch.NewBackend(indexName, tc.Client())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("takes every record of that space out of the index", func() {
|
||||
gone := opensearchtest.Testdata.Resources.File
|
||||
tc.Require.DocumentCreate(indexName, gone.ID, strings.NewReader(opensearchtest.JSONMustMarshal(GinkgoTB(), gone)))
|
||||
|
||||
stays := opensearchtest.Testdata.Resources.File
|
||||
stays.ID = "1$2!3"
|
||||
stays.RootID = "1$2!2"
|
||||
tc.Require.DocumentCreate(indexName, stays.ID, strings.NewReader(opensearchtest.JSONMustMarshal(GinkgoTB(), stays)))
|
||||
|
||||
tc.Require.IndicesCount([]string{indexName}, nil, 2)
|
||||
|
||||
Expect(backend.PurgeSpace(gone.RootID)).To(Succeed())
|
||||
|
||||
tc.Require.IndicesRefresh([]string{indexName}, nil)
|
||||
left := opensearchtest.SearchHitsMustBeConverted[search.Resource](
|
||||
GinkgoTB(),
|
||||
tc.Require.Search(indexName, strings.NewReader(`{"query":{"match_all":{}}}`)).Hits,
|
||||
)
|
||||
Expect(left).To(HaveLen(1), "only the records of that space are gone")
|
||||
Expect(left[0].ID).To(Equal(stays.ID))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DocCount", func() {
|
||||
const indexName = "opencloud-test-engine-doc-count"
|
||||
|
||||
|
||||
@@ -325,6 +325,57 @@ func (_c *Engine_Purge_Call) RunAndReturn(run func(id string, onlyDeleted bool)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PurgeSpace provides a mock function for the type Engine
|
||||
func (_mock *Engine) PurgeSpace(rootID string) error {
|
||||
ret := _mock.Called(rootID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PurgeSpace")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = returnFunc(rootID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Engine_PurgeSpace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PurgeSpace'
|
||||
type Engine_PurgeSpace_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PurgeSpace is a helper method to define mock.On call
|
||||
// - rootID string
|
||||
func (_e *Engine_Expecter) PurgeSpace(rootID interface{}) *Engine_PurgeSpace_Call {
|
||||
return &Engine_PurgeSpace_Call{Call: _e.mock.On("PurgeSpace", rootID)}
|
||||
}
|
||||
|
||||
func (_c *Engine_PurgeSpace_Call) Run(run func(rootID string)) *Engine_PurgeSpace_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Engine_PurgeSpace_Call) Return(err error) *Engine_PurgeSpace_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Engine_PurgeSpace_Call) RunAndReturn(run func(rootID string) error) *Engine_PurgeSpace_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Restore provides a mock function for the type Engine
|
||||
func (_mock *Engine) Restore(id string) error {
|
||||
ret := _mock.Called(id)
|
||||
|
||||
@@ -227,6 +227,57 @@ func (_c *Searcher_PurgeItem_Call) RunAndReturn(run func(ref *providerv1beta1.Re
|
||||
return _c
|
||||
}
|
||||
|
||||
// PurgeSpace provides a mock function for the type Searcher
|
||||
func (_mock *Searcher) PurgeSpace(spaceID *providerv1beta1.StorageSpaceId) error {
|
||||
ret := _mock.Called(spaceID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PurgeSpace")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(*providerv1beta1.StorageSpaceId) error); ok {
|
||||
r0 = returnFunc(spaceID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Searcher_PurgeSpace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PurgeSpace'
|
||||
type Searcher_PurgeSpace_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PurgeSpace is a helper method to define mock.On call
|
||||
// - spaceID *providerv1beta1.StorageSpaceId
|
||||
func (_e *Searcher_Expecter) PurgeSpace(spaceID interface{}) *Searcher_PurgeSpace_Call {
|
||||
return &Searcher_PurgeSpace_Call{Call: _e.mock.On("PurgeSpace", spaceID)}
|
||||
}
|
||||
|
||||
func (_c *Searcher_PurgeSpace_Call) Run(run func(spaceID *providerv1beta1.StorageSpaceId)) *Searcher_PurgeSpace_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *providerv1beta1.StorageSpaceId
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*providerv1beta1.StorageSpaceId)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Searcher_PurgeSpace_Call) Return(err error) *Searcher_PurgeSpace_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Searcher_PurgeSpace_Call) RunAndReturn(run func(spaceID *providerv1beta1.StorageSpaceId) error) *Searcher_PurgeSpace_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RestoreItem provides a mock function for the type Searcher
|
||||
func (_mock *Searcher) RestoreItem(ref *providerv1beta1.Reference) {
|
||||
_mock.Called(ref)
|
||||
|
||||
@@ -33,6 +33,7 @@ type Engine interface {
|
||||
Delete(id string) error
|
||||
Restore(id string) error
|
||||
Purge(id string, onlyDeleted bool) error
|
||||
PurgeSpace(rootID string) error
|
||||
|
||||
NewBatch(batchSize int) (BatchOperator, error)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ type Searcher interface {
|
||||
Search(ctx context.Context, req *searchsvc.SearchRequest) (*searchsvc.SearchResponse, error)
|
||||
|
||||
IndexSpace(spaceID *provider.StorageSpaceId, forceRescan bool) error
|
||||
PurgeSpace(spaceID *provider.StorageSpaceId) error
|
||||
PurgeDeleted(spaceID *provider.StorageSpaceId) error
|
||||
|
||||
TrashItem(resourceID *provider.ResourceId)
|
||||
@@ -561,6 +562,31 @@ func (s *Service) PurgeItem(ref *provider.Reference) {
|
||||
logDocCount(s.engine, s.logger)
|
||||
}
|
||||
|
||||
func (s *Service) PurgeSpace(spaceID *provider.StorageSpaceId) error {
|
||||
if spaceID == nil {
|
||||
return fmt.Errorf("spaceID must not be nil")
|
||||
}
|
||||
|
||||
rootID, err := storagespace.ParseID(spaceID.GetOpaqueId())
|
||||
if err != nil {
|
||||
s.logger.Error().Err(err).Str("space_id", spaceID.GetOpaqueId()).Msg("invalid space id")
|
||||
return err
|
||||
}
|
||||
if rootID.StorageId == "" || rootID.SpaceId == "" {
|
||||
return fmt.Errorf("invalid space id %s", spaceID.GetOpaqueId())
|
||||
}
|
||||
rootID.OpaqueId = rootID.SpaceId
|
||||
|
||||
if err := s.engine.PurgeSpace(storagespace.FormatResourceID(&rootID)); err != nil {
|
||||
s.logger.Error().Err(err).Str("space_id", spaceID.GetOpaqueId()).Msg("failed to purge the space from the index")
|
||||
return err
|
||||
}
|
||||
|
||||
logDocCount(s.engine, s.logger)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) PurgeDeleted(spaceID *provider.StorageSpaceId) error {
|
||||
if spaceID == nil {
|
||||
return fmt.Errorf("spaceID must not be nil")
|
||||
|
||||
@@ -7,14 +7,15 @@ import (
|
||||
"time"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/metrics"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/raw"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/metrics"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
||||
)
|
||||
|
||||
var tracer trace.Tracer
|
||||
@@ -61,6 +62,7 @@ func New(ctx context.Context, stream raw.Stream, logger log.Logger, tp trace.Tra
|
||||
events.TagsAdded{},
|
||||
events.TagsRemoved{},
|
||||
events.SpaceRenamed{},
|
||||
events.SpaceDeleted{},
|
||||
events.LabelAdded{},
|
||||
events.LabelRemoved{},
|
||||
},
|
||||
@@ -200,6 +202,9 @@ func (s Service) processEvent(e raw.Event) error {
|
||||
s.indexSpaceDebouncer.Debounce(getSpaceID(ev.FileRef), e.Ack)
|
||||
case events.SpaceRenamed:
|
||||
s.indexSpaceDebouncer.Debounce(ev.ID, e.Ack)
|
||||
case events.SpaceDeleted:
|
||||
s.index.PurgeSpace(ev.ID)
|
||||
e.Ack()
|
||||
case events.LabelAdded:
|
||||
s.index.UpsertItem(ev.Ref)
|
||||
case events.LabelRemoved:
|
||||
|
||||
@@ -50,6 +50,7 @@ var _ = DescribeTable("event",
|
||||
return int(calls.Load())
|
||||
}, "2s").Should(Equal(len(mcks)))
|
||||
},
|
||||
Entry("SpaceDeleted", []string{"PurgeSpace"}, events.SpaceDeleted{}, false),
|
||||
Entry("ItemTrashed", []string{"TrashItem", "IndexSpace"}, events.ItemTrashed{}, false),
|
||||
Entry("ItemMoved", []string{"MoveItem", "IndexSpace"}, events.ItemMoved{}, false),
|
||||
Entry("ItemRestored", []string{"RestoreItem", "IndexSpace"}, events.ItemRestored{}, false),
|
||||
|
||||
Reference in new issue
Block a user