Skip to content

Commit 9211fe3

Browse files
authored
Implement greedyness deactivation and skips (#2086)
Signed-off-by: jose.vazquez <[email protected]>
1 parent e51a244 commit 9211fe3

File tree

3 files changed

+102
-42
lines changed

3 files changed

+102
-42
lines changed

internal/controller/atlasproject/network_peering.go

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package atlasproject
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"fmt"
78
"net/http"
@@ -15,6 +16,7 @@ import (
1516
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/provider"
1617
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status"
1718
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/compare"
19+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/customresource"
1820
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/workflow"
1921
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer"
2022
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/paging"
@@ -27,17 +29,61 @@ const (
2729
StatusTerminating = "TERMINATING"
2830
)
2931

32+
var errNortFound = errors.New("not found")
33+
3034
type networkPeerDiff struct {
3135
PeersToDelete []string
3236
PeersToCreate []akov2.NetworkPeer
3337
PeersToUpdate []admin.BaseNetworkPeeringConnectionSettings
3438
}
3539

40+
func isSkippedNetworkPeersEmpty(atlasProject *akov2.AtlasProject) (bool, error) {
41+
lastSkippedSpec := akov2.AtlasProjectSpec{}
42+
lastSkippedSpecString, ok := atlasProject.Annotations[customresource.AnnotationLastSkippedConfiguration]
43+
if !ok {
44+
return false, nil
45+
}
46+
47+
if err := json.Unmarshal([]byte(lastSkippedSpecString), &lastSkippedSpec); err != nil {
48+
return false, fmt.Errorf("failed to parse last skipped configuration: %w", err)
49+
}
50+
51+
return len(lastSkippedSpec.NetworkPeers) == 0, nil
52+
}
53+
54+
func hasLastAppliedNetworkPeers(atlasProject *akov2.AtlasProject) (bool, error) {
55+
lastAppliedSpec := akov2.AtlasProjectSpec{}
56+
lastAppliedSpecStr, ok := atlasProject.Annotations[customresource.AnnotationLastAppliedConfiguration]
57+
if !ok {
58+
return false, nil
59+
}
60+
61+
if err := json.Unmarshal([]byte(lastAppliedSpecStr), &lastAppliedSpec); err != nil {
62+
return false, fmt.Errorf("failed to parse last applied configuration: %w", err)
63+
}
64+
65+
return len(lastAppliedSpec.NetworkPeers) > 0, nil
66+
}
67+
3668
func ensureNetworkPeers(workflowCtx *workflow.Context, akoProject *akov2.AtlasProject) workflow.Result {
69+
shouldSkip, err := isSkippedNetworkPeersEmpty(akoProject)
70+
if err != nil {
71+
return workflow.Terminate(workflow.Internal, err)
72+
}
73+
if shouldSkip {
74+
workflowCtx.UnsetCondition(api.NetworkPeerReadyType)
75+
return workflow.OK()
76+
}
77+
78+
configuredBefore, err := hasLastAppliedNetworkPeers(akoProject)
79+
if err != nil {
80+
return workflow.Terminate(workflow.Internal, err)
81+
}
82+
3783
networkPeerStatus := akoProject.Status.DeepCopy().NetworkPeers
3884
networkPeerSpec := akoProject.Spec.DeepCopy().NetworkPeers
3985

40-
result, condition := SyncNetworkPeer(workflowCtx, akoProject.ID(), networkPeerStatus, networkPeerSpec)
86+
result, condition := SyncNetworkPeer(workflowCtx, akoProject.ID(), networkPeerStatus, networkPeerSpec, configuredBefore)
4187
if !result.IsOk() {
4288
workflowCtx.SetConditionFromResult(condition, result)
4389
return result
@@ -68,7 +114,7 @@ func failedPeerStatus(errMessage string, peer akov2.NetworkPeer) status.AtlasNet
68114
}
69115
}
70116

71-
func SyncNetworkPeer(workflowCtx *workflow.Context, groupID string, peerStatuses []status.AtlasNetworkPeer, peerSpecs []akov2.NetworkPeer) (workflow.Result, api.ConditionType) {
117+
func SyncNetworkPeer(workflowCtx *workflow.Context, groupID string, peerStatuses []status.AtlasNetworkPeer, peerSpecs []akov2.NetworkPeer, configuredBefore bool) (workflow.Result, api.ConditionType) {
72118
defer workflowCtx.EnsureStatusOption(status.AtlasProjectSetNetworkPeerOption(&peerStatuses))
73119
logger := workflowCtx.Log
74120
mongoClient := workflowCtx.SdkClient
@@ -100,11 +146,13 @@ func SyncNetworkPeer(workflowCtx *workflow.Context, groupID string, peerStatuses
100146
return workflow.Terminate(workflow.ProjectNetworkPeerIsNotReadyInAtlas,
101147
errors.New("failed to update network peer statuses")), api.NetworkPeerReadyType
102148
}
103-
err = deleteUnusedContainers(workflowCtx.Context, mongoClient.NetworkPeeringApi, groupID, getPeerIDs(peerStatuses))
104-
if err != nil {
105-
logger.Errorf("failed to delete unused containers: %v", err)
106-
return workflow.Terminate(workflow.ProjectNetworkPeerIsNotReadyInAtlas,
107-
fmt.Errorf("failed to delete unused containers: %w", err)), api.NetworkPeerReadyType
149+
if configuredBefore {
150+
err = deleteUnusedContainers(workflowCtx.Context, mongoClient.NetworkPeeringApi, groupID, getPeerIDs(peerStatuses))
151+
if err != nil {
152+
logger.Errorf("failed to delete unused containers: %v", err)
153+
return workflow.Terminate(workflow.ProjectNetworkPeerIsNotReadyInAtlas,
154+
fmt.Errorf("failed to delete unused containers: %w", err)), api.NetworkPeerReadyType
155+
}
108156
}
109157
return ensurePeerStatus(peerStatuses, len(peerSpecs), logger), api.NetworkPeerReadyType
110158
}
@@ -410,8 +458,11 @@ func comparePeersPair(ctx context.Context, existedPeer, expectedPeer akov2.Netwo
410458
}
411459

412460
func deletePeerByID(ctx context.Context, peerService admin.NetworkPeeringApi, groupID string, containerID string, logger *zap.SugaredLogger) error {
413-
_, _, err := peerService.DeletePeeringConnection(ctx, groupID, containerID).Execute()
461+
_, response, err := peerService.DeletePeeringConnection(ctx, groupID, containerID).Execute()
414462
if err != nil {
463+
if response.StatusCode == http.StatusNotFound {
464+
return errors.Join(err, errNortFound)
465+
}
415466
logger.Errorf("failed to delete peering container %s: %v", containerID, err)
416467
return err
417468
}
@@ -548,27 +599,23 @@ func validateInitNetworkPeer(peer akov2.NetworkPeer) error {
548599
return fmt.Errorf("unsupported provider: %s", peer.ProviderName)
549600
}
550601

551-
func DeleteAllNetworkPeers(ctx context.Context, groupID string, service admin.NetworkPeeringApi, logger *zap.SugaredLogger) workflow.Result {
552-
result := workflow.OK()
553-
err := deleteAllNetworkPeers(ctx, groupID, service, logger)
602+
func DeleteOwnedNetworkPeers(ctx context.Context, project *akov2.AtlasProject, service admin.NetworkPeeringApi, logger *zap.SugaredLogger) workflow.Result {
603+
shouldSkip, err := isSkippedNetworkPeersEmpty(project)
554604
if err != nil {
555-
result = workflow.Terminate(workflow.ProjectNetworkPeerIsNotReadyInAtlas, errors.New("failed to delete NetworkPeers"))
605+
workflow.Terminate(workflow.ProjectNetworkPeerIsNotReadyInAtlas,
606+
fmt.Errorf("failed to delete NetworkPeers: %w", err))
556607
}
557-
return result
558-
}
559-
560-
func deleteAllNetworkPeers(ctx context.Context, groupID string, service admin.NetworkPeeringApi, logger *zap.SugaredLogger) error {
561-
peers, err := GetAllExistedNetworkPeer(ctx, service, groupID)
562-
if err != nil {
563-
logger.Errorf("failed to list network peers for project %s: %v", groupID, err)
564-
return err
608+
if shouldSkip {
609+
logger.Debug("Nothing to do, Network Peers projects subresouedes are disabled")
610+
return workflow.OK()
565611
}
566-
for _, peer := range peers {
567-
errDelete := deletePeerByID(ctx, service, groupID, peer.GetId(), logger)
568-
if errDelete != nil {
569-
logger.Errorf("failed to delete network peer %s: %v", peer.GetId(), errDelete)
570-
return errDelete
612+
for _, peerStatus := range project.Status.NetworkPeers {
613+
errDelete := deletePeerByID(ctx, service, project.ID(), peerStatus.ID, logger)
614+
if errDelete != nil && !errors.Is(errDelete, errNortFound) {
615+
logger.Errorf("failed to delete network peer %s: %v", peerStatus.ID, errDelete)
616+
return workflow.Terminate(workflow.ProjectNetworkPeerIsNotReadyInAtlas,
617+
fmt.Errorf("failed to delete NetworkPeers: %w", errDelete))
571618
}
572619
}
573-
return nil
620+
return workflow.OK()
574621
}

internal/controller/atlasproject/project.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (r *AtlasProjectReconciler) delete(ctx *workflow.Context, services *AtlasPr
9797
if result := DeleteAllPrivateEndpoints(ctx, atlasProject); !result.IsOk() {
9898
return r.terminate(ctx, workflow.ServerlessPrivateEndpointReady, errors.New(result.GetMessage()))
9999
}
100-
if result := DeleteAllNetworkPeers(ctx.Context, atlasProject.ID(), ctx.SdkClient.NetworkPeeringApi, ctx.Log); !result.IsOk() {
100+
if result := DeleteOwnedNetworkPeers(ctx.Context, atlasProject, ctx.SdkClient.NetworkPeeringApi, ctx.Log); !result.IsOk() {
101101
return r.terminate(ctx, workflow.ProjectNetworkPeerIsNotReadyInAtlas, errors.New(result.GetMessage()))
102102
}
103103

internal/controller/atlasproject/project_test.go

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package atlasproject
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"testing"
78

@@ -150,16 +151,8 @@ func TestHandleProject(t *testing.T) {
150151
ListPrivateEndpointServicesExecute(admin.ListPrivateEndpointServicesApiRequest{ApiService: mockPrivateEndpointAPI}).
151152
Return([]admin.EndpointService{}, nil, nil)
152153

153-
mockPeeringEndpointAPI := mockadmin.NewNetworkPeeringApi(t)
154-
mockPeeringEndpointAPI.EXPECT().ListPeeringConnectionsWithParams(mock.Anything, mock.Anything).
155-
Return(admin.ListPeeringConnectionsApiRequest{ApiService: mockPeeringEndpointAPI})
156-
mockPeeringEndpointAPI.EXPECT().
157-
ListPeeringConnectionsExecute(admin.ListPeeringConnectionsApiRequest{ApiService: mockPeeringEndpointAPI}.PageNum(1)).
158-
Return(&admin.PaginatedContainerPeer{}, nil, nil)
159-
160154
return &admin.APIClient{
161155
PrivateEndpointServicesApi: mockPrivateEndpointAPI,
162-
NetworkPeeringApi: mockPeeringEndpointAPI,
163156
}
164157
},
165158
projectServiceMocker: func() project.ProjectService {
@@ -472,6 +465,15 @@ func TestHandleProject(t *testing.T) {
472465
Name: "my-project",
473466
Namespace: "default",
474467
Finalizers: []string{customresource.FinalizerLabel},
468+
Annotations: map[string]string{
469+
// no skipped config, but some fake applied condig on network peerings
470+
customresource.AnnotationLastAppliedConfiguration: func() string {
471+
d, _ := json.Marshal(&akov2.AtlasProjectSpec{
472+
NetworkPeers: []akov2.NetworkPeer{{}},
473+
})
474+
return string(d)
475+
}(),
476+
},
475477
},
476478
Spec: akov2.AtlasProjectSpec{
477479
Name: "my-project",
@@ -578,6 +580,15 @@ func TestHandleProject(t *testing.T) {
578580
Name: "my-project",
579581
Namespace: "default",
580582
Finalizers: []string{customresource.FinalizerLabel},
583+
// no skipped config, but some fake applied condig on network peerings
584+
Annotations: map[string]string{
585+
customresource.AnnotationLastAppliedConfiguration: func() string {
586+
d, _ := json.Marshal(&akov2.AtlasProjectSpec{
587+
NetworkPeers: []akov2.NetworkPeer{{}},
588+
})
589+
return string(d)
590+
}(),
591+
},
581592
},
582593
Spec: akov2.AtlasProjectSpec{
583594
Name: "my-project",
@@ -686,6 +697,15 @@ func TestHandleProject(t *testing.T) {
686697
Name: "my-project",
687698
Namespace: "default",
688699
Finalizers: []string{customresource.FinalizerLabel},
700+
// no skipped config, but some fake applied condig on network peerings
701+
Annotations: map[string]string{
702+
customresource.AnnotationLastAppliedConfiguration: func() string {
703+
d, _ := json.Marshal(&akov2.AtlasProjectSpec{
704+
NetworkPeers: []akov2.NetworkPeer{{}},
705+
})
706+
return string(d)
707+
}(),
708+
},
689709
},
690710
Spec: akov2.AtlasProjectSpec{
691711
Name: "my-project",
@@ -1093,15 +1113,8 @@ func TestDelete(t *testing.T) {
10931113
ListPrivateEndpointServicesExecute(admin.ListPrivateEndpointServicesApiRequest{ApiService: mockPrivateEndpointAPI}).
10941114
Return([]admin.EndpointService{}, nil, nil)
10951115

1096-
mockPeeringEndpointAPI := mockadmin.NewNetworkPeeringApi(t)
1097-
mockPeeringEndpointAPI.EXPECT().ListPeeringConnectionsWithParams(mock.Anything, mock.Anything).
1098-
Return(admin.ListPeeringConnectionsApiRequest{ApiService: mockPeeringEndpointAPI}.PageNum(1))
1099-
mockPeeringEndpointAPI.EXPECT().
1100-
ListPeeringConnectionsExecute(admin.ListPeeringConnectionsApiRequest{ApiService: mockPeeringEndpointAPI}.PageNum(1)).
1101-
Return(&admin.PaginatedContainerPeer{}, nil, nil)
11021116
return &admin.APIClient{
11031117
PrivateEndpointServicesApi: mockPrivateEndpointAPI,
1104-
NetworkPeeringApi: mockPeeringEndpointAPI,
11051118
}
11061119
},
11071120
projectServiceMocker: func() project.ProjectService {

0 commit comments

Comments
 (0)