Skip to content

Commit

Permalink
Added unit tests for searchIndexes state machine
Browse files Browse the repository at this point in the history
  • Loading branch information
igor-karpukhin committed Apr 29, 2024
1 parent 28df782 commit 615bdb1
Show file tree
Hide file tree
Showing 6 changed files with 558 additions and 9 deletions.
10 changes: 10 additions & 0 deletions internal/searchindex/searchindex.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package searchindex

import (
"bytes"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
Expand All @@ -24,6 +26,14 @@ type SearchIndex struct {
Status *string
}

func (s *SearchIndex) Key() string {
var buf bytes.Buffer
encoder := gob.NewEncoder(&buf)
err := encoder.Encode(*s)
fmt.Println("ERR:", err)
return buf.String()
}

func (s *SearchIndex) GetID() string {
return pointer.GetOrDefault(s.ID, "")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/v1/status/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const (

const (
SearchIndexesNamesAreNotUnique = "SearchIndexesNamesAreNotUnique"
SearchIndexesNotAllReady = "SearchIndexesNotAllReady"
SearchIndexesSomeNotReady = "SearchIndexesSomeNotReady"
)

// Generic condition type
Expand Down
106 changes: 106 additions & 0 deletions pkg/controller/atlasdeployment/searchindex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,110 @@ func Test_searchIndexReconciler(t *testing.T) {
result := reconciler.Reconcile(nil, nil, []error{fmt.Errorf("testError")})
assert.False(t, result.IsOk())
})

t.Run("Must return InProgress if index status is anything but ACTIVE", func(t *testing.T) {
reconciler := &searchIndexReconciler{
ctx: &workflow.Context{
Log: zap.S(),
OrgID: "testOrgID",
SdkClient: &admin.APIClient{},
Context: context.Background(),
},
deployment: nil,
k8sClient: nil,
projectID: "",
indexName: "testIndexName",
}
result := reconciler.Reconcile(nil, &searchindex.SearchIndex{Status: pointer.MakePtr("NOT STARTED")}, nil)
assert.True(t, result.IsInProgress())
})

t.Run("Must not call update API if indexes are equal", func(t *testing.T) {
reconciler := &searchIndexReconciler{
ctx: &workflow.Context{
Log: zap.S(),
OrgID: "testOrgID",
SdkClient: &admin.APIClient{},
Context: context.Background(),
},
deployment: nil,
k8sClient: nil,
projectID: "",
indexName: "testIndexName",
}
idx := &searchindex.SearchIndex{
SearchIndex: akov2.SearchIndex{},
AtlasSearchIndexConfigSpec: akov2.AtlasSearchIndexConfigSpec{},
ID: nil,
Status: nil,
}
result := reconciler.Reconcile(idx, idx, nil)
assert.True(t, result.IsOk())
})

t.Run("Must trigger index update if state in AKO and in Atlas is different", func(t *testing.T) {
mockSearchAPI := mockadmin.NewAtlasSearchApi(t)
mockSearchAPI.EXPECT().
UpdateAtlasSearchIndex(context.Background(), mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(admin.UpdateAtlasSearchIndexApiRequest{ApiService: mockSearchAPI})
mockSearchAPI.EXPECT().
UpdateAtlasSearchIndexExecute(admin.UpdateAtlasSearchIndexApiRequest{ApiService: mockSearchAPI}).
Return(
&admin.ClusterSearchIndex{Status: pointer.MakePtr("NOT STARTED")},
&http.Response{StatusCode: http.StatusCreated}, nil,
)

testCluster := &akov2.AtlasDeployment{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "testDeployment",
Namespace: "testNamespace",
},
Spec: akov2.AtlasDeploymentSpec{
DeploymentSpec: &akov2.AdvancedDeploymentSpec{
Name: "testDeploymentName",
},
},
Status: status.AtlasDeploymentStatus{},
}

reconciler := &searchIndexReconciler{
ctx: &workflow.Context{
Log: zap.S(),
OrgID: "testOrgID",
SdkClient: &admin.APIClient{
AtlasSearchApi: mockSearchAPI,
},
Context: context.Background(),
},
deployment: testCluster,
k8sClient: nil,
projectID: "",
indexName: "testIndexName",
}
idxInAtlas := &searchindex.SearchIndex{
SearchIndex: akov2.SearchIndex{
Name: "testIndex",
},
AtlasSearchIndexConfigSpec: akov2.AtlasSearchIndexConfigSpec{},
ID: pointer.MakePtr("testID"),
Status: nil,
}
idxInAKO := &searchindex.SearchIndex{
SearchIndex: akov2.SearchIndex{
Name: "testIndex",
Search: &akov2.Search{
Synonyms: nil,
Mappings: &akov2.Mappings{
Dynamic: pointer.MakePtr(true),
},
},
},
AtlasSearchIndexConfigSpec: akov2.AtlasSearchIndexConfigSpec{},
ID: pointer.MakePtr("testID"),
Status: nil,
}
result := reconciler.Reconcile(idxInAKO, idxInAtlas, nil)
assert.True(t, result.IsInProgress())
})
}
32 changes: 24 additions & 8 deletions pkg/controller/atlasdeployment/searchindexes.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func verifyAllIndexesNamesAreUnique(indexes []akov2.SearchIndex) bool {
return true
}

// TODO: remove if won't refactor to the action-based SM
//func findIndexesIntersection(akoIndexes, atlasIndexes []*searchindex.SearchIndex, intersection IntersectionType) []searchindex.SearchIndex {
// var result []searchindex.SearchIndex
// switch intersection {
Expand Down Expand Up @@ -234,14 +235,19 @@ func (sr *searchIndexesReconciler) Reconcile() workflow.Result {
continue
}
indexInternal = searchindex.NewSearchIndexFromAKO(akoIndex, &idxConfig.Spec)
// TODO: add finalizer to the Config
case IndexTypeVector:
// Vector index doesn't require any external configuration
indexInternal = searchindex.NewSearchIndexFromAKO(akoIndex, &akov2.AtlasSearchIndexConfigSpec{})
default:
e := fmt.Errorf("index %q has unknown type %q. Can be either %s or %s",
akoIndex.Name, akoIndex.Type, IndexTypeSearch, IndexTypeVector)
indexesErrors.Add(akoIndex.Name, e)
}
akoIndexes[akoIndex.Name] = indexInternal
}

var allIndexes map[string]*searchindex.SearchIndex
allIndexes := map[string]*searchindex.SearchIndex{}
// note: the order matters! first Atlas, then AKO so we have most up-to-date desired state
maps.Copy(allIndexes, atlasIndexes)
maps.Copy(allIndexes, akoIndexes)
Expand All @@ -256,11 +262,11 @@ func (sr *searchIndexesReconciler) Reconcile() workflow.Result {

var akoIdx, atlasIdx *searchindex.SearchIndex

if _, ok := akoIndexes[current.Name]; ok {
akoIdx = current
if val, ok := akoIndexes[current.Name]; ok {
akoIdx = val
}
if _, ok := atlasIndexes[current.Name]; ok {
atlasIdx = current
if val, ok := atlasIndexes[current.Name]; ok {
atlasIdx = val
}

results = append(results, (&searchIndexReconciler{
Expand All @@ -273,8 +279,8 @@ func (sr *searchIndexesReconciler) Reconcile() workflow.Result {
}

for i := range results {
if !results[i].IsOk() {
return sr.terminate(status.SearchIndexesNotAllReady, nil)
if results[i].IsInProgress() || !results[i].IsOk() {
return sr.progress()
}
}

Expand All @@ -283,11 +289,21 @@ func (sr *searchIndexesReconciler) Reconcile() workflow.Result {

func (sr *searchIndexesReconciler) terminate(reason workflow.ConditionReason, err error) workflow.Result {
sr.ctx.Log.Error(err)
result := workflow.Terminate(reason, err.Error())
var errMsg string
if err != nil {
errMsg = err.Error()
}
result := workflow.Terminate(reason, errMsg)
sr.ctx.SetConditionFromResult(status.SearchIndexesReadyType, result)
return result
}

func (sr *searchIndexesReconciler) progress() workflow.Result {
result := workflow.InProgress(status.SearchIndexesSomeNotReady, "not all indexes are in READY state")
sr.ctx.SetConditionFromResult(status.SearchIndexStatusReady, result)
return result
}

func (sr *searchIndexesReconciler) empty() workflow.Result {
sr.ctx.UnsetCondition(status.SearchIndexesReadyType)
return workflow.OK()
Expand Down
Loading

0 comments on commit 615bdb1

Please sign in to comment.