From 2d2f0f7f4a1da6cebb93b5c47e7249873c67880e Mon Sep 17 00:00:00 2001 From: Nikolay Yordanov Date: Fri, 17 May 2024 13:31:47 +0300 Subject: [PATCH 1/5] Initial implementation --- components/director/cmd/director/main.go | 10 +-- .../domain/assignmentoperation/service.go | 11 ++++ .../assignmentoperation/service_test.go | 63 +++++++++++++++++++ .../internal/domain/formation/service.go | 19 ++++-- .../asynchronous_flow_control_operator.go | 44 ++++++++++--- .../operators/config_mutator_operator_test.go | 4 +- .../operators/constraint_engine.go | 10 ++- .../operators/constraint_engine_test.go | 2 +- .../contains_scenario_groups_operator_test.go | 2 +- .../destination_creator_operator_test.go | 2 +- ..._assignment_notification_for_loops_test.go | 2 +- ..._formation_assignment_notification_test.go | 2 +- ...ntain_resource_of_subtype_operator_test.go | 2 +- ..._to_any_formation_of_type_operator_test.go | 2 +- .../redirect_notification_operator_test.go | 2 +- .../director/internal/domain/root_resolver.go | 2 +- .../internal/formationmapping/handler.go | 41 ++++++------ .../internal/model/assignment_operation.go | 2 + .../internal/model/formation_assignment.go | 4 +- .../tenantfetchersvc/resync/builder.go | 2 +- .../director/pkg/formationassignment/util.go | 9 +++ 21 files changed, 178 insertions(+), 59 deletions(-) diff --git a/components/director/cmd/director/main.go b/components/director/cmd/director/main.go index 79d5e9d584..0999378886 100644 --- a/components/director/cmd/director/main.go +++ b/components/director/cmd/director/main.go @@ -777,7 +777,7 @@ func runtimeSvc(transact persistence.Transactioner, cfg config, tenantMappingCon asaSvc := scenarioassignment.NewService(asaRepo, labelDefinitionSvc) tenantSvc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelSvc, tenantConverter) formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(appRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) @@ -855,7 +855,7 @@ func runtimeCtxSvc(transact persistence.Transactioner, cfg config, securedHTTPCl certSubjectInputBuilder := databuilder.NewWebhookCertSubjectBuilder(certSubjectMappingRepo) webhookDataInputBuilder := databuilder.NewWebhookDataInputBuilder(appRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, webhookLabelBuilder, webhookTenantBuilder, certSubjectInputBuilder) formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(appRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) @@ -993,7 +993,7 @@ func applicationSvc(transact persistence.Transactioner, cfg config, securedHTTPC certSubjectInputBuilder := databuilder.NewWebhookCertSubjectBuilder(certSubjectMappingRepo) webhookDataInputBuilder := databuilder.NewWebhookDataInputBuilder(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, webhookLabelBuilder, webhookTenantBuilder, certSubjectInputBuilder) formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tntSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tntSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) @@ -1077,7 +1077,7 @@ func createFormationMappingAuthenticator(transact persistence.Transactioner, cfg formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) destinationCreatorSvc := destinationcreator.NewService(mtlsHTTPClient, destinationCreatorConfig, applicationRepo(), runtimeRepo, runtimeContextRepo, labelRepo, tenantRepo) destinationSvc := destination.NewService(transact, destinationRepo, tenantRepo, uidSvc, destinationCreatorSvc) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, destinationSvc, destinationCreatorSvc, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, destinationSvc, destinationCreatorSvc, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, nil, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) faNotificationSvc := formationassignment.NewFormationAssignmentNotificationService(formationAssignmentRepo, webhookConverter, webhookRepo, tenantRepo, webhookDataInputBuilder, formationRepo, notificationsBuilder, runtimeContextRepo, labelSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) @@ -1153,7 +1153,7 @@ func createFormationMappingHandler(transact persistence.Transactioner, appRepo a formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) destinationCreatorSvc := destinationcreator.NewService(mtlsHTTPClient, destinationCreatorConfig, applicationRepo(), runtimeRepo, runtimeContextRepo, labelRepo, tenantRepo) destinationSvc := destination.NewService(transact, destinationRepo, tenantRepo, uidSvc, destinationCreatorSvc) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, destinationSvc, destinationCreatorSvc, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, asaSvc, destinationSvc, destinationCreatorSvc, systemAuthSvc, formationRepo, labelRepo, labelSvc, appRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(appRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) diff --git a/components/director/internal/domain/assignmentoperation/service.go b/components/director/internal/domain/assignmentoperation/service.go index f897f481c1..4bc114c86b 100644 --- a/components/director/internal/domain/assignmentoperation/service.go +++ b/components/director/internal/domain/assignmentoperation/service.go @@ -76,6 +76,17 @@ func (s *service) Finish(ctx context.Context, assignmentID, formationID string) return nil } +// GetLatestOperation gets the latest Operation for the provided Assignment and Formation ID +func (s *service) GetLatestOperation(ctx context.Context, assignmentID, formationID string) (*model.AssignmentOperation, error) { + log.C(ctx).Infof("Getting the latest operation for assignment with ID: %s and formation with ID: %s", assignmentID, formationID) + + latestOperation, err := s.repo.GetLatestOperation(ctx, assignmentID, formationID) + if err != nil { + return nil, errors.Wrapf(err, "while getting the latest operation for assignment with ID: %s, formation with ID: %s", assignmentID, formationID) + } + return latestOperation, nil +} + // Update updates the Assignment Operation's triggered_by field with the new trigger and sets the started_at timestamp to the current time func (s *service) Update(ctx context.Context, assignmentID, formationID string, newTrigger model.OperationTrigger) error { operation, err := s.repo.GetLatestOperation(ctx, assignmentID, formationID) diff --git a/components/director/internal/domain/assignmentoperation/service_test.go b/components/director/internal/domain/assignmentoperation/service_test.go index d7e0eccf99..9e8816b59b 100644 --- a/components/director/internal/domain/assignmentoperation/service_test.go +++ b/components/director/internal/domain/assignmentoperation/service_test.go @@ -175,6 +175,69 @@ func TestService_Finish(t *testing.T) { } } +func TestService_GetLatestOperation(t *testing.T) { + ctx := context.TODO() + + testErr := errors.New("test error") + + testCases := []struct { + Name string + InputAssignmentID string + InputFormationID string + AssignmentOperationRepo func() *automock.AssignmentOperationRepository + ExpectedOutput *model.AssignmentOperation + ExpectedErrorMsg string + }{ + { + Name: "Success", + InputAssignmentID: assignmentID, + InputFormationID: formationID, + AssignmentOperationRepo: func() *automock.AssignmentOperationRepository { + repo := &automock.AssignmentOperationRepository{} + repo.On("GetLatestOperation", ctx, assignmentID, formationID).Return(fixAssignmentOperationModel(), nil).Once() + return repo + }, + ExpectedOutput: fixAssignmentOperationModel(), + }, + { + Name: "Error when getting latest operation", + InputAssignmentID: assignmentID, + InputFormationID: formationID, + AssignmentOperationRepo: func() *automock.AssignmentOperationRepository { + repo := &automock.AssignmentOperationRepository{} + repo.On("GetLatestOperation", ctx, assignmentID, formationID).Return(nil, testErr).Once() + return repo + }, + ExpectedErrorMsg: testErr.Error(), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + aoRepo := &automock.AssignmentOperationRepository{} + if testCase.AssignmentOperationRepo != nil { + aoRepo = testCase.AssignmentOperationRepo() + } + + svc := assignmentOperation.NewService(aoRepo, nil) + + // WHEN + output, err := svc.GetLatestOperation(ctx, testCase.InputAssignmentID, testCase.InputFormationID) + + if testCase.ExpectedErrorMsg != "" { + require.Error(t, err) + require.Contains(t, err.Error(), testCase.ExpectedErrorMsg) + } else { + require.NoError(t, err) + } + + require.Equal(t, testCase.ExpectedOutput, output) + + mock.AssertExpectationsForObjects(t, aoRepo) + }) + } +} + func TestService_Update(t *testing.T) { ctx := context.TODO() diff --git a/components/director/internal/domain/formation/service.go b/components/director/internal/domain/formation/service.go index 36684f1158..5ce62e163d 100644 --- a/components/director/internal/domain/formation/service.go +++ b/components/director/internal/domain/formation/service.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/kyma-incubator/compass/components/director/pkg/apperrors" + formationassignmentpkg "github.com/kyma-incubator/compass/components/director/pkg/formationassignment" "github.com/kyma-incubator/compass/components/director/pkg/formationconstraint" "github.com/kyma-incubator/compass/components/director/pkg/log" "github.com/kyma-incubator/compass/components/director/pkg/persistence" @@ -166,6 +167,7 @@ type asaEngine interface { type assignmentOperationService interface { Create(ctx context.Context, in *model.AssignmentOperationInput) (string, error) Finish(ctx context.Context, assignmentID, formationID string) error + GetLatestOperation(ctx context.Context, assignmentID, formationID string) (*model.AssignmentOperation, error) Update(ctx context.Context, assignmentID, formationID string, newTrigger model.OperationTrigger) error ListByFormationAssignmentIDs(ctx context.Context, formationAssignmentIDs []string, pageSize int, cursor string) ([]*model.AssignmentOperationPage, error) DeleteByIDs(ctx context.Context, ids []string) error @@ -1327,9 +1329,14 @@ func (s *service) resynchronizeFormationAssignmentNotifications(ctx context.Cont logger := log.C(ctx).WithField(log.FieldFormationAssignmentID, fa.ID) ctx = log.ContextWithLogger(ctx, logger) - operation := fa.GetOperation() + latestAssignmentOperation, err := s.assignmentOperationService.GetLatestOperation(ctx, fa.ID, fa.FormationID) + if err != nil { + return err + } + formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(latestAssignmentOperation.Type) // todo::: placeholder + var notificationForReverseFA *webhookclient.FormationAssignmentNotificationRequest - notificationForFA, err := s.formationAssignmentNotificationService.GenerateFormationAssignmentNotification(ctxWithTransact, fa, operation) + notificationForFA, err := s.formationAssignmentNotificationService.GenerateFormationAssignmentNotification(ctxWithTransact, fa, formationOperation) if err != nil { return err } @@ -1339,7 +1346,7 @@ func (s *service) resynchronizeFormationAssignmentNotifications(ctx context.Cont return err } if reverseFA != nil { - notificationForReverseFA, err = s.formationAssignmentNotificationService.GenerateFormationAssignmentNotification(ctxWithTransact, reverseFA, operation) + notificationForReverseFA, err = s.formationAssignmentNotificationService.GenerateFormationAssignmentNotification(ctxWithTransact, reverseFA, formationOperation) if err != nil && !apperrors.IsNotFoundError(err) { return err } @@ -1354,7 +1361,7 @@ func (s *service) resynchronizeFormationAssignmentNotifications(ctx context.Cont } faClone := fa.Clone() - if notificationForFA != nil && operation == model.UnassignFormation { + if notificationForFA != nil && formationOperation == model.UnassignFormation { faClone.SetStateToDeleting() formationassignment.ResetAssignmentConfigAndError(faClone) if err := s.formationAssignmentService.Update(ctxWithTransact, faClone.ID, faClone); err != nil { @@ -1364,7 +1371,7 @@ func (s *service) resynchronizeFormationAssignmentNotifications(ctx context.Cont return errors.Wrapf(err, "while updating %s Operation for assignment with ID: %s triggered by %s", model.Unassign, faClone.ID, assignmentOperationTriggeredBy) } } - if operation == model.AssignFormation { + if formationOperation == model.AssignFormation { faClone.State = string(model.InitialAssignmentState) // Cleanup the error if present as new notification will be sent. The previous configuration should be left intact. faClone.Error = nil @@ -1384,7 +1391,7 @@ func (s *service) resynchronizeFormationAssignmentNotifications(ctx context.Cont }, ReverseAssignmentReqMapping: reverseReqMapping, }, - Operation: operation, + Operation: formationOperation, } // We separate the assignment pairs in 3 groups diff --git a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go index 338060e11d..fecd3238e7 100644 --- a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go +++ b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go @@ -52,8 +52,12 @@ func (e *ConstraintEngine) AsynchronousFlowControlOperator(ctx context.Context, if err != nil { return false, err } - if formationAssignment.State == string(model.InstanceCreatorDeletingAssignmentState) || - formationAssignment.State == string(model.InstanceCreatorDeleteErrorAssignmentState) { + latestAssignmentOperation, err := e.assignmentOperationService.GetLatestOperation(ctx, formationAssignment.ID, formationAssignment.FormationID) + if err != nil { + return false, err + } + + if latestAssignmentOperation.Type == model.InstanceCreatorUnassign { // todo::: placeholder log.C(ctx).Infof("Tenant mapping participant processing unassign notification has alredy finished, redirecting notification for assignment %q with state %q to instance creator", formationAssignment.ID, formationAssignment.State) ri.ShouldRedirect = true return e.RedirectNotification(ctx, &ri.RedirectNotificationInput) @@ -102,12 +106,24 @@ func (e *ConstraintEngine) AsynchronousFlowControlOperator(ctx context.Context, if err != nil { log.C(ctx).Warnf(errors.Wrapf(err, "Reverse assignment not found").Error()) } - log.C(ctx).Infof("Tenant mapping participant finished processing unassign notification successfully for assignment with ID %q, changing state to %q", formationAssignment.ID, model.InstanceCreatorDeletingAssignmentState) - formationAssignment.State = string(model.InstanceCreatorDeletingAssignmentState) - statusReport.State = string(model.InstanceCreatorDeletingAssignmentState) - if err = e.formationAssignmentRepo.Update(ctx, formationAssignment); err != nil { - return false, errors.Wrapf(err, "while updating formation assignment with ID %q", formationAssignment.ID) + + log.C(ctx).Infof("Tenant mapping participant finished processing unassign notification successfully for assignment with ID %q, will create new %q Assignment Operation", formationAssignment.ID, model.InstanceCreatorUnassign) // todo::: log that you will create the IC_UNASSIGN OP + statusReport.State = string(model.DeletingAssignmentState) // todo::: set to DELETING state so that in DeleteWithConstraints we don't delete the FA + //if err = e.formationAssignmentRepo.Update(ctx, formationAssignment); err != nil { + // return false, errors.Wrapf(err, "while updating formation assignment with ID %q", formationAssignment.ID) + //} // todo:::delete + + // todo::: create INSTANCE_CREATOR_UNASSIGN operation + _, err = e.assignmentOperationService.Create(ctx, &model.AssignmentOperationInput{ + Type: model.InstanceCreatorUnassign, + FormationAssignmentID: formationAssignment.ID, + FormationID: formationAssignment.FormationID, + TriggeredBy: model.UnassignObject, + }) + if err != nil { + return false, errors.Wrapf(err, "while creating %s Operation for assignment with ID: %s", model.InstanceCreatorUnassign, formationAssignment.ID) } + log.C(ctx).Infof("Generating formation assignment notification for assignent with ID %q", formationAssignment.ID) assignmentPair, err := e.formationAssignmentNotificationSvc.GenerateFormationAssignmentPair(ctx, formationAssignment, reverseAssignment, model.UnassignFormation) if err != nil { @@ -124,12 +140,20 @@ func (e *ConstraintEngine) AsynchronousFlowControlOperator(ctx context.Context, if formationAssignment.State == string(model.DeletingAssignmentState) && statusReport.State == string(model.DeleteErrorAssignmentState) { return true, nil } - if formationAssignment.State == string(model.InstanceCreatorDeletingAssignmentState) && statusReport.State == string(model.ReadyAssignmentState) { + + latestAssignmentOperation, err := e.assignmentOperationService.GetLatestOperation(ctx, formationAssignment.ID, formationAssignment.FormationID) + if err != nil { + return false, err + } + + if latestAssignmentOperation.Type == model.InstanceCreatorUnassign && statusReport.State == string(model.ReadyAssignmentState) { // todo::: instead of checking the FA state, get its latest operation and check if it's INSTANCE_CREATOR_UNASSIGN log.C(ctx).Infof("Instance creator reported %q, proceeding with deletion of formation assignment with ID %q", statusReport.State, formationAssignment.ID) return true, nil } - if formationAssignment.State == string(model.InstanceCreatorDeletingAssignmentState) && statusReport.State == string(model.DeleteErrorFormationState) { - statusReport.State = string(model.InstanceCreatorDeleteErrorAssignmentState) + if latestAssignmentOperation.Type == model.InstanceCreatorUnassign && statusReport.State == string(model.DeleteErrorFormationState) { // todo::: instead of checking the FA state, get its latest operation and check if it's INSTANCE_CREATOR_UNASSIGN + //statusReport.State = string(model.InstanceCreatorDeleteErrorAssignmentState) // todo::: delete + // //todo::: create INSTANCE_CREATOR_UNASSIGN operation ; UPDATE: skip + log.C(ctx).Infof("Instance creator reported %q for formation assignment with ID %q", statusReport.State, formationAssignment.ID) return true, nil } } diff --git a/components/director/internal/domain/formationconstraint/operators/config_mutator_operator_test.go b/components/director/internal/domain/formationconstraint/operators/config_mutator_operator_test.go index 5920d8c69c..5cd57fce27 100644 --- a/components/director/internal/domain/formationconstraint/operators/config_mutator_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/config_mutator_operator_test.go @@ -119,7 +119,7 @@ func TestConstraintOperators_ConfigMutator(t *testing.T) { if testCase.LabelSvcFn != nil { labelService = testCase.LabelSvcFn() } - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelService, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelService, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) // WHEN input := fixConfigMutatorInput(testCase.InputFa, testCase.StatusReport, testCase.NewState, testCase.NewConfig, testCase.OnlyForSourceSubtypes) @@ -145,7 +145,7 @@ func TestConstraintOperators_ConfigMutator(t *testing.T) { t.Run("Error when incorrect input is provided", func(t *testing.T) { // GIVEN - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) // WHEN input := &formationconstraintpkg.DestinationCreatorInput{} diff --git a/components/director/internal/domain/formationconstraint/operators/constraint_engine.go b/components/director/internal/domain/formationconstraint/operators/constraint_engine.go index d01d7b0e8a..5c8a433aa2 100644 --- a/components/director/internal/domain/formationconstraint/operators/constraint_engine.go +++ b/components/director/internal/domain/formationconstraint/operators/constraint_engine.go @@ -107,6 +107,12 @@ type formationAssignmentNotificationService interface { GenerateFormationAssignmentPair(ctx context.Context, fa, reverseFA *model.FormationAssignment, operation model.FormationOperation) (*formationassignment.AssignmentMappingPairWithOperation, error) } +//go:generate mockery --exported --name=assignmentOperationService --output=automock --outpkg=automock --case=underscore --disable-version-string +type assignmentOperationService interface { + Create(ctx context.Context, in *model.AssignmentOperationInput) (string, error) + GetLatestOperation(ctx context.Context, assignmentID, formationID string) (*model.AssignmentOperation, error) +} + // OperatorInput represents the input needed by the constraint operator type OperatorInput interface{} @@ -137,6 +143,7 @@ type ConstraintEngine struct { formationAssignmentRepo formationAssignmentRepository formationAssignmentService formationAssignmentService formationAssignmentNotificationSvc formationAssignmentNotificationService + assignmentOperationService assignmentOperationService operators map[OperatorName]OperatorFunc operatorInputConstructors map[OperatorName]OperatorInputConstructor runtimeTypeLabelKey string @@ -144,7 +151,7 @@ type ConstraintEngine struct { } // NewConstraintEngine returns new ConstraintEngine -func NewConstraintEngine(transact persistence.Transactioner, constraintSvc formationConstraintSvc, tenantSvc tenantService, asaSvc automaticScenarioAssignmentService, destinationSvc destinationService, destinationCreatorSvc destinationCreatorService, systemAuthSvc systemAuthService, formationRepo formationRepository, labelRepo labelRepository, labelService labelService, applicationRepository applicationRepository, runtimeContextRepo runtimeContextRepo, formationTemplateRepo formationTemplateRepo, formationAssignmentRepo formationAssignmentRepository, formationAssignmentService formationAssignmentService, formationAssignmentNotificationSvc formationAssignmentNotificationService, runtimeTypeLabelKey string, applicationTypeLabelKey string) *ConstraintEngine { +func NewConstraintEngine(transact persistence.Transactioner, constraintSvc formationConstraintSvc, tenantSvc tenantService, asaSvc automaticScenarioAssignmentService, destinationSvc destinationService, destinationCreatorSvc destinationCreatorService, systemAuthSvc systemAuthService, formationRepo formationRepository, labelRepo labelRepository, labelService labelService, applicationRepository applicationRepository, runtimeContextRepo runtimeContextRepo, formationTemplateRepo formationTemplateRepo, formationAssignmentRepo formationAssignmentRepository, formationAssignmentService formationAssignmentService, formationAssignmentNotificationSvc formationAssignmentNotificationService, assignmentOperationService assignmentOperationService, runtimeTypeLabelKey string, applicationTypeLabelKey string) *ConstraintEngine { ce := &ConstraintEngine{ transact: transact, constraintSvc: constraintSvc, @@ -162,6 +169,7 @@ func NewConstraintEngine(transact persistence.Transactioner, constraintSvc forma formationAssignmentRepo: formationAssignmentRepo, formationAssignmentService: formationAssignmentService, formationAssignmentNotificationSvc: formationAssignmentNotificationSvc, + assignmentOperationService: assignmentOperationService, operatorInputConstructors: map[OperatorName]OperatorInputConstructor{ IsNotAssignedToAnyFormationOfTypeOperator: NewIsNotAssignedToAnyFormationOfTypeInput, DoesNotContainResourceOfSubtypeOperator: NewDoesNotContainResourceOfSubtypeInput, diff --git a/components/director/internal/domain/formationconstraint/operators/constraint_engine_test.go b/components/director/internal/domain/formationconstraint/operators/constraint_engine_test.go index f201edaead..35636141ca 100644 --- a/components/director/internal/domain/formationconstraint/operators/constraint_engine_test.go +++ b/components/director/internal/domain/formationconstraint/operators/constraint_engine_test.go @@ -124,7 +124,7 @@ func TestConstraintEngine_EnforceConstraints(t *testing.T) { t.Run(testCase.Name, func(t *testing.T) { formationConstraintSvc := testCase.FormationConstraintService() - engine := operators.NewConstraintEngine(nil, formationConstraintSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, formationConstraintSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) if testCase.OperatorFunc != nil { engine.SetOperator(testCase.OperatorFunc) } else { diff --git a/components/director/internal/domain/formationconstraint/operators/contains_scenario_groups_operator_test.go b/components/director/internal/domain/formationconstraint/operators/contains_scenario_groups_operator_test.go index e1bc6bc5dd..5a3c225522 100644 --- a/components/director/internal/domain/formationconstraint/operators/contains_scenario_groups_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/contains_scenario_groups_operator_test.go @@ -244,7 +244,7 @@ func TestConstraintOperators_ContainsScenarioGroups(t *testing.T) { t.Run(testCase.Name, func(t *testing.T) { systemAuthSvc := testCase.SystemAuthService() appRepo := testCase.ApplicationRepo() - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, systemAuthSvc, nil, nil, nil, appRepo, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, systemAuthSvc, nil, nil, nil, appRepo, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) result, err := engine.ContainsScenarioGroups(ctx, testCase.Input) diff --git a/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go b/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go index d527f367f3..30f07a3d77 100644 --- a/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go @@ -417,7 +417,7 @@ func TestConstraintOperators_DestinationCreator(t *testing.T) { destCreatorSvc = testCase.DestinationCreatorSvc() } - engine := operators.NewConstraintEngine(nil, nil, nil, nil, destSvc, destCreatorSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, destSvc, destCreatorSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) // WHEN result, err := engine.DestinationCreator(ctx, testCase.Input) diff --git a/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_for_loops_test.go b/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_for_loops_test.go index 6a2a7566bb..ec6f590f85 100644 --- a/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_for_loops_test.go +++ b/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_for_loops_test.go @@ -118,7 +118,7 @@ func TestConstraintOperators_DoNotGenerateFormationAssignmentNotificationForLoop if testCase.FormationTemplateRepo != nil { formationTemplateRepo = testCase.FormationTemplateRepo() } - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelSvc, nil, runtimeContextRepo, formationTemplateRepo, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelSvc, nil, runtimeContextRepo, formationTemplateRepo, nil, nil, nil, nil, runtimeType, applicationType) result, err := engine.DoNotGenerateFormationAssignmentNotificationForLoops(ctx, testCase.Input) diff --git a/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_test.go b/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_test.go index b498dbd30d..ed07276e10 100644 --- a/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_test.go +++ b/components/director/internal/domain/formationconstraint/operators/do_not_generate_formation_assignment_notification_test.go @@ -178,7 +178,7 @@ func TestConstraintOperators_DoNotGenerateFormationAssignmentNotification(t *tes if testCase.FormationTemplateRepo != nil { formationTemplateRepo = testCase.FormationTemplateRepo() } - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelSvc, nil, runtimeContextRepo, formationTemplateRepo, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelSvc, nil, runtimeContextRepo, formationTemplateRepo, nil, nil, nil, nil, runtimeType, applicationType) result, err := engine.DoNotGenerateFormationAssignmentNotification(ctx, testCase.Input) diff --git a/components/director/internal/domain/formationconstraint/operators/does_not_contain_resource_of_subtype_operator_test.go b/components/director/internal/domain/formationconstraint/operators/does_not_contain_resource_of_subtype_operator_test.go index 4545219bd2..e3863e1c9d 100644 --- a/components/director/internal/domain/formationconstraint/operators/does_not_contain_resource_of_subtype_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/does_not_contain_resource_of_subtype_operator_test.go @@ -121,7 +121,7 @@ func TestConstraintOperators_DoesNotContainResourceOfSubtype(t *testing.T) { t.Run(testCase.Name, func(t *testing.T) { labelSvc := testCase.LabelSvc() appRepo := testCase.ApplicationRepo() - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelSvc, appRepo, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, labelSvc, appRepo, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) result, err := engine.DoesNotContainResourceOfSubtype(ctx, testCase.Input) diff --git a/components/director/internal/domain/formationconstraint/operators/is_not_assigned_to_any_formation_of_type_operator_test.go b/components/director/internal/domain/formationconstraint/operators/is_not_assigned_to_any_formation_of_type_operator_test.go index cbcaa4829c..74d12bc19b 100644 --- a/components/director/internal/domain/formationconstraint/operators/is_not_assigned_to_any_formation_of_type_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/is_not_assigned_to_any_formation_of_type_operator_test.go @@ -231,7 +231,7 @@ func TestConstraintOperators_IsNotAssignedToAnyFormationOfType(t *testing.T) { formationRepo = testCase.FormationRepositoryFn() } - engine := operators.NewConstraintEngine(nil, nil, tenantSvc, asaSvc, nil, nil, nil, formationRepo, labelRepo, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, tenantSvc, asaSvc, nil, nil, nil, formationRepo, labelRepo, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) // WHEN result, err := engine.IsNotAssignedToAnyFormationOfType(ctx, testCase.Input) diff --git a/components/director/internal/domain/formationconstraint/operators/redirect_notification_operator_test.go b/components/director/internal/domain/formationconstraint/operators/redirect_notification_operator_test.go index 0d88cf813e..dc8685c02a 100644 --- a/components/director/internal/domain/formationconstraint/operators/redirect_notification_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/redirect_notification_operator_test.go @@ -43,7 +43,7 @@ func TestConstraintOperators_RedirectNotification(t *testing.T) { for _, ts := range testCases { t.Run(ts.Name, func(t *testing.T) { // GIVEN - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) // WHEN result, err := engine.RedirectNotification(ctx, ts.Input) diff --git a/components/director/internal/domain/root_resolver.go b/components/director/internal/domain/root_resolver.go index 35b4f7b15b..1526edc507 100644 --- a/components/director/internal/domain/root_resolver.go +++ b/components/director/internal/domain/root_resolver.go @@ -275,7 +275,7 @@ func NewRootResolver( formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, constraintReferencesRepo, uidSvc, formationConstraintConverter) destinationCreatorSvc := destinationcreator.NewService(mtlsHTTPClient, destinationCreatorConfig, applicationRepo, runtimeRepo, runtimeContextRepo, labelRepo, tenantRepo) destinationSvc := destination.NewService(transact, destinationRepo, tenantRepo, uidSvc, destinationCreatorSvc) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, destinationSvc, destinationCreatorSvc, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, featuresConfig.RuntimeTypeLabelKey, featuresConfig.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, destinationSvc, destinationCreatorSvc, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, featuresConfig.RuntimeTypeLabelKey, featuresConfig.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, featuresConfig.RuntimeTypeLabelKey, featuresConfig.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) diff --git a/components/director/internal/formationmapping/handler.go b/components/director/internal/formationmapping/handler.go index 82a144e3f3..adfbd48f5b 100644 --- a/components/director/internal/formationmapping/handler.go +++ b/components/director/internal/formationmapping/handler.go @@ -8,8 +8,6 @@ import ( "github.com/kyma-incubator/compass/components/director/internal/domain/statusreport" - "github.com/kyma-incubator/compass/components/director/pkg/str" - "github.com/kyma-incubator/compass/components/director/pkg/apperrors" "github.com/kyma-incubator/compass/components/director/pkg/graphql" @@ -45,6 +43,7 @@ type formationAssignmentStatusService interface { type assignmentOperationService interface { Create(ctx context.Context, in *model.AssignmentOperationInput) (string, error) Finish(ctx context.Context, assignmentID, formationID string) error + GetLatestOperation(ctx context.Context, assignmentID, formationID string) (*model.AssignmentOperation, error) } type malformedRequest struct { @@ -190,8 +189,15 @@ func (h *Handler) updateFormationAssignmentStatus(w http.ResponseWriter, r *http return } - formationOperation := determineOperationBasedOnFormationAssignmentState(fa) - notificationStatusReport := newNotificationStatusReportFromRequestBody(assignmentReqBody, fa) + latestAssignmentOperation, err := h.assignmentOperationService.GetLatestOperation(ctx, fa.ID, fa.FormationID) + if err != nil { + log.C(ctx).WithError(err).Errorf("Error when getting latest operation for assignment with ID: %s. X-Request-ID: %s", fa.ID, correlationID) + respondWithError(ctx, w, http.StatusInternalServerError, errResp) + return + } + + formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(latestAssignmentOperation.Type) // todo::: placeholder + notificationStatusReport := newNotificationStatusReportFromRequestBody(assignmentReqBody, fa, latestAssignmentOperation.Type) originalStateFromStatusReport := notificationStatusReport.State if !isStateSupportedForOperation(ctx, model.FormationAssignmentState(originalStateFromStatusReport), formationOperation, formation.State, reset) { log.C(ctx).Errorf("An invalid state: %q is provided for %q operation with reset option %t", originalStateFromStatusReport, formationOperation, reset) @@ -275,7 +281,7 @@ func (h *Handler) updateFormationAssignmentStatus(w http.ResponseWriter, r *http if formationOperation == model.UnassignFormation { log.C(ctx).Infof("Processing status update for formation assignment with ID: %s during %q operation", fa.ID, model.UnassignFormation) - isFADeleted, err := h.processFormationAssignmentUnassignStatusUpdate(ctx, fa, notificationStatusReport) + isFADeleted, err := h.processFormationAssignmentUnassignStatusUpdate(ctx, fa, notificationStatusReport, latestAssignmentOperation.Type) if commitErr := tx.Commit(); commitErr != nil { log.C(ctx).WithError(err).Error("An error occurred while closing database transaction") @@ -524,10 +530,10 @@ func (b FormationRequestBody) Validate() error { } // processFormationAssignmentUnassignStatusUpdate handles the async unassign formation assignment status update -func (h *Handler) processFormationAssignmentUnassignStatusUpdate(ctx context.Context, fa *model.FormationAssignment, statusReport *statusreport.NotificationStatusReport) (bool, error) { +func (h *Handler) processFormationAssignmentUnassignStatusUpdate(ctx context.Context, fa *model.FormationAssignment, statusReport *statusreport.NotificationStatusReport, latestAssignmentOperationType model.AssignmentOperationType) (bool, error) { stateFromStatusReport := model.FormationAssignmentState(statusReport.State) - if !fa.IsInRegularUnassignState() { + if latestAssignmentOperationType == model.InstanceCreatorUnassign { // todo::: there won't be IC state anymore so think how to adapt -> maybe get fa latestOP and if it's IC_UNASSIGN enter the if consumerInfo, err := consumer.LoadFromContext(ctx) if err != nil { return false, err @@ -775,17 +781,6 @@ func decodeJSONBody(w http.ResponseWriter, r *http.Request, dst interface{}) err return nil } -func determineOperationBasedOnFormationAssignmentState(fa *model.FormationAssignment) model.FormationOperation { - assignOperationStates := []string{string(model.InitialAssignmentState), - string(model.ConfigPendingAssignmentState), - string(model.ReadyAssignmentState), - string(model.CreateErrorAssignmentState)} - if str.ValueIn(fa.State, assignOperationStates) { - return model.AssignFormation - } - return model.UnassignFormation -} - func (mr *malformedRequest) Error() string { return mr.msg } @@ -795,11 +790,11 @@ type responseError struct { errorMessage string } -func newNotificationStatusReportFromRequestBody(requestBody FormationAssignmentRequestBody, fa *model.FormationAssignment) *statusreport.NotificationStatusReport { - return statusreport.NewNotificationStatusReport(requestBody.Configuration, calculateState(requestBody, fa), requestBody.Error) +func newNotificationStatusReportFromRequestBody(requestBody FormationAssignmentRequestBody, fa *model.FormationAssignment, assignmentOperationType model.AssignmentOperationType) *statusreport.NotificationStatusReport { + return statusreport.NewNotificationStatusReport(requestBody.Configuration, calculateState(requestBody, fa, assignmentOperationType), requestBody.Error) } -func calculateState(requestBody FormationAssignmentRequestBody, fa *model.FormationAssignment) string { +func calculateState(requestBody FormationAssignmentRequestBody, fa *model.FormationAssignment, assignmentOperationType model.AssignmentOperationType) string { if requestBody.State != "" { return string(requestBody.State) } @@ -808,9 +803,9 @@ func calculateState(requestBody FormationAssignmentRequestBody, fa *model.Format return fa.State } - operation := determineOperationBasedOnFormationAssignmentState(fa) + formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(assignmentOperationType) // todo::: placeholder - if operation == model.AssignFormation { + if formationOperation == model.AssignFormation { return string(model.CreateErrorAssignmentState) } diff --git a/components/director/internal/model/assignment_operation.go b/components/director/internal/model/assignment_operation.go index 41d7d5d4f8..092a07290e 100644 --- a/components/director/internal/model/assignment_operation.go +++ b/components/director/internal/model/assignment_operation.go @@ -14,6 +14,8 @@ const ( Assign AssignmentOperationType = "ASSIGN" // Unassign denotes the operation is unassigning object from a formation Unassign AssignmentOperationType = "UNASSIGN" + // InstanceCreatorUnassign denotes the operation is related to Instance Creator unassign + InstanceCreatorUnassign AssignmentOperationType = "INSTANCE_CREATOR_UNASSIGN" ) // OperationTrigger denotes what triggered the operation - assign, unassign, reset etc. diff --git a/components/director/internal/model/formation_assignment.go b/components/director/internal/model/formation_assignment.go index 0ded8bae1b..cac8c0ecf2 100644 --- a/components/director/internal/model/formation_assignment.go +++ b/components/director/internal/model/formation_assignment.go @@ -156,7 +156,7 @@ func (fa *FormationAssignment) IsInProgressState() bool { } // IsInRegularUnassignState returns if the formation assignment is in regular unassign stage -func (fa *FormationAssignment) IsInRegularUnassignState() bool { +func (fa *FormationAssignment) IsInRegularUnassignState() bool { // todo::: can be deleted unassignOperationStates := []string{string(DeletingAssignmentState), string(DeleteErrorAssignmentState)} return str.ValueIn(fa.State, unassignOperationStates) @@ -176,7 +176,7 @@ func (fa *FormationAssignment) SetStateToDeleting() bool { } // GetOperation returns the formation operation that is determined based on the state of the assignment -func (fa *FormationAssignment) GetOperation() FormationOperation { +func (fa *FormationAssignment) GetOperation() FormationOperation { // todo::: can be deleted operation := AssignFormation if strings.HasSuffix(fa.State, string(DeleteErrorAssignmentState)) || strings.HasSuffix(fa.State, string(DeletingAssignmentState)) { operation = UnassignFormation diff --git a/components/director/internal/tenantfetchersvc/resync/builder.go b/components/director/internal/tenantfetchersvc/resync/builder.go index 595a6d0d2b..0197fd18d7 100644 --- a/components/director/internal/tenantfetchersvc/resync/builder.go +++ b/components/director/internal/tenantfetchersvc/resync/builder.go @@ -170,7 +170,7 @@ func (b *synchronizerBuilder) domainServices(featuresConfig features.Config) (Te formationAssignmentConv := formationassignment.NewConverter() formationAssignmentRepo := formationassignment.NewRepository(formationAssignmentConv) formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) - constraintEngine := operators.NewConstraintEngine(b.transact, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, featuresConfig.RuntimeTypeLabelKey, featuresConfig.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(b.transact, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, featuresConfig.RuntimeTypeLabelKey, featuresConfig.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, featuresConfig.RuntimeTypeLabelKey, featuresConfig.ApplicationTypeLabelKey) faNotificationSvc := formationassignment.NewFormationAssignmentNotificationService(formationAssignmentRepo, webhookConverter, webhookRepo, tenantRepo, nil, formationRepo, notificationsBuilder, runtimeContextRepo, labelSvc, featuresConfig.RuntimeTypeLabelKey, featuresConfig.ApplicationTypeLabelKey) formationAssignmentStatusSvc := formationassignment.NewFormationAssignmentStatusService(formationAssignmentRepo, constraintEngine, faNotificationSvc) diff --git a/components/director/pkg/formationassignment/util.go b/components/director/pkg/formationassignment/util.go index aaac7b17a4..5b61b1e14d 100644 --- a/components/director/pkg/formationassignment/util.go +++ b/components/director/pkg/formationassignment/util.go @@ -1,5 +1,7 @@ package formationassignment +import "github.com/kyma-incubator/compass/components/director/internal/model" + // IsConfigEmpty checks for different "empty" json values that could be in the formation assignment configuration func IsConfigEmpty(configuration string) bool { if configuration == "" || configuration == "{}" || configuration == "\"\"" || configuration == "[]" || configuration == "null" { @@ -8,3 +10,10 @@ func IsConfigEmpty(configuration string) bool { return false } + +func DetermineFormationOperationFromLatestAssignmentOperation(assignmentOperationType model.AssignmentOperationType) model.FormationOperation { + if assignmentOperationType == model.Assign { + return model.AssignFormation + } + return model.UnassignFormation +} From 920e90185bb9ac8f17d71cfc9c7a73991061fcea Mon Sep 17 00:00:00 2001 From: Nikolay Yordanov Date: Wed, 22 May 2024 10:45:23 +0300 Subject: [PATCH 2/5] Finalize implementation and add tests --- components/director/cmd/ns-adapter/main.go | 2 +- components/director/cmd/ordaggregator/main.go | 2 +- components/director/cmd/systemfetcher/main.go | 2 +- .../assignmentoperation/fixtures_test.go | 2 - .../automock/assignment_operation_service.go | 46 +-- .../formation/finalize_formation_test.go | 11 + .../domain/formation/fixtures_test.go | 17 +- .../internal/domain/formation/service.go | 4 +- .../internal/domain/formation/service_test.go | 107 ++++++- .../domain/formationassignment/converter.go | 4 +- .../formationassignment/converter_test.go | 10 - .../formationassignment/fixtures_test.go | 2 +- .../formationassignment/notifications.go | 3 +- .../formationassignment/service_test.go | 2 +- .../status_service_test.go | 23 +- .../asynchronous_flow_control_operator.go | 24 +- ...asynchronous_flow_control_operator_test.go | 192 ++++++----- .../automock/assignment_operation_service.go | 79 +++++ .../operators/fixtures_test.go | 30 +- .../automock/assignment_operation_service.go | 34 +- .../formationmapping/fixtures_test.go | 17 + .../internal/formationmapping/handler.go | 4 +- .../internal/formationmapping/handler_test.go | 298 +++++++++++++++--- .../internal/model/formation_assignment.go | 10 +- .../pkg/webhook/application_tenant_mapping.go | 4 +- .../webhook/formation_configuration_change.go | 4 +- ...4_drop_instance_creator_fa_states.down.sql | 30 ++ ...734_drop_instance_creator_fa_states.up.sql | 33 ++ ...tion_only_notifications_new_format_test.go | 17 +- .../operations/cleanup_notifications.go | 5 +- .../operations/unassign_application.go | 3 +- 31 files changed, 785 insertions(+), 236 deletions(-) create mode 100644 components/director/internal/domain/formationconstraint/operators/automock/assignment_operation_service.go create mode 100644 components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.down.sql create mode 100644 components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.up.sql diff --git a/components/director/cmd/ns-adapter/main.go b/components/director/cmd/ns-adapter/main.go index 6db252843e..ddd8588aac 100644 --- a/components/director/cmd/ns-adapter/main.go +++ b/components/director/cmd/ns-adapter/main.go @@ -191,7 +191,7 @@ func main() { certSubjectInputBuilder := databuilder.NewWebhookCertSubjectBuilder(certSubjectMappingRepo) webhookDataInputBuilder := databuilder.NewWebhookDataInputBuilder(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, webhookLabelBuilder, webhookTenantBuilder, certSubjectInputBuilder) formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tntSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, conf.RuntimeTypeLabelKey, conf.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tntSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, conf.RuntimeTypeLabelKey, conf.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, conf.RuntimeTypeLabelKey, conf.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) diff --git a/components/director/cmd/ordaggregator/main.go b/components/director/cmd/ordaggregator/main.go index b0407326d1..20a9fcf87c 100644 --- a/components/director/cmd/ordaggregator/main.go +++ b/components/director/cmd/ordaggregator/main.go @@ -325,7 +325,7 @@ func main() { webhookDataInputBuilder := databuilder.NewWebhookDataInputBuilder(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, webhookLabelBuilder, webhookTenantBuilder, certSubjectInputBuilder) formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) systemAuthSvc := systemauth.NewService(systemAuthRepo, uidSvc) - constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) diff --git a/components/director/cmd/systemfetcher/main.go b/components/director/cmd/systemfetcher/main.go index f30dc0e21c..3898a85891 100644 --- a/components/director/cmd/systemfetcher/main.go +++ b/components/director/cmd/systemfetcher/main.go @@ -575,7 +575,7 @@ func createSystemFetcher(ctx context.Context, cfg config, cfgProvider *configpro certSubjectInputBuilder := databuilder.NewWebhookCertSubjectBuilder(certSubjectMappingRepo) webhookDataInputBuilder := databuilder.NewWebhookDataInputBuilder(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, webhookLabelBuilder, webhookTenantBuilder, certSubjectInputBuilder) formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter) - constraintEngine := operators.NewConstraintEngine(tx, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) + constraintEngine := operators.NewConstraintEngine(tx, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, nil, nil, systemAuthSvc, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, nil, nil, assignmentOperationSvc, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, cfg.Features.RuntimeTypeLabelKey, cfg.Features.ApplicationTypeLabelKey) notificationsGenerator := formation.NewNotificationsGenerator(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder) notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo, formationAssignmentRepo, formationRepo) diff --git a/components/director/internal/domain/assignmentoperation/fixtures_test.go b/components/director/internal/domain/assignmentoperation/fixtures_test.go index d882eb699f..69bc1f7ee1 100644 --- a/components/director/internal/domain/assignmentoperation/fixtures_test.go +++ b/components/director/internal/domain/assignmentoperation/fixtures_test.go @@ -9,8 +9,6 @@ import ( "github.com/kyma-incubator/compass/components/director/pkg/graphql" ) -const () - var ( operationID = "operationID" operationID2 = "operationID2" diff --git a/components/director/internal/domain/formation/automock/assignment_operation_service.go b/components/director/internal/domain/formation/automock/assignment_operation_service.go index c8dee26f0b..ab5f4e58d5 100644 --- a/components/director/internal/domain/formation/automock/assignment_operation_service.go +++ b/components/director/internal/domain/formation/automock/assignment_operation_service.go @@ -19,10 +19,6 @@ type AssignmentOperationService struct { func (_m *AssignmentOperationService) Create(ctx context.Context, in *model.AssignmentOperationInput) (string, error) { ret := _m.Called(ctx, in) - if len(ret) == 0 { - panic("no return value specified for Create") - } - var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.AssignmentOperationInput) (string, error)); ok { @@ -47,10 +43,6 @@ func (_m *AssignmentOperationService) Create(ctx context.Context, in *model.Assi func (_m *AssignmentOperationService) DeleteByIDs(ctx context.Context, ids []string) error { ret := _m.Called(ctx, ids) - if len(ret) == 0 { - panic("no return value specified for DeleteByIDs") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { r0 = rf(ctx, ids) @@ -65,10 +57,6 @@ func (_m *AssignmentOperationService) DeleteByIDs(ctx context.Context, ids []str func (_m *AssignmentOperationService) Finish(ctx context.Context, assignmentID string, formationID string) error { ret := _m.Called(ctx, assignmentID, formationID) - if len(ret) == 0 { - panic("no return value specified for Finish") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, assignmentID, formationID) @@ -79,14 +67,36 @@ func (_m *AssignmentOperationService) Finish(ctx context.Context, assignmentID s return r0 } +// GetLatestOperation provides a mock function with given fields: ctx, assignmentID, formationID +func (_m *AssignmentOperationService) GetLatestOperation(ctx context.Context, assignmentID string, formationID string) (*model.AssignmentOperation, error) { + ret := _m.Called(ctx, assignmentID, formationID) + + var r0 *model.AssignmentOperation + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.AssignmentOperation, error)); ok { + return rf(ctx, assignmentID, formationID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *model.AssignmentOperation); ok { + r0 = rf(ctx, assignmentID, formationID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AssignmentOperation) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, assignmentID, formationID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ListByFormationAssignmentIDs provides a mock function with given fields: ctx, formationAssignmentIDs, pageSize, cursor func (_m *AssignmentOperationService) ListByFormationAssignmentIDs(ctx context.Context, formationAssignmentIDs []string, pageSize int, cursor string) ([]*model.AssignmentOperationPage, error) { ret := _m.Called(ctx, formationAssignmentIDs, pageSize, cursor) - if len(ret) == 0 { - panic("no return value specified for ListByFormationAssignmentIDs") - } - var r0 []*model.AssignmentOperationPage var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string, int, string) ([]*model.AssignmentOperationPage, error)); ok { @@ -113,10 +123,6 @@ func (_m *AssignmentOperationService) ListByFormationAssignmentIDs(ctx context.C func (_m *AssignmentOperationService) Update(ctx context.Context, assignmentID string, formationID string, newTrigger model.OperationTrigger) error { ret := _m.Called(ctx, assignmentID, formationID, newTrigger) - if len(ret) == 0 { - panic("no return value specified for Update") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, model.OperationTrigger) error); ok { r0 = rf(ctx, assignmentID, formationID, newTrigger) diff --git a/components/director/internal/domain/formation/finalize_formation_test.go b/components/director/internal/domain/formation/finalize_formation_test.go index 853df8124f..3bfacfa0ad 100644 --- a/components/director/internal/domain/formation/finalize_formation_test.go +++ b/components/director/internal/domain/formation/finalize_formation_test.go @@ -40,6 +40,9 @@ func TestServiceFinalizeDraftFormation(t *testing.T) { fa4 := fixFormationAssignmentModelWithParameters("id4", FormationID, RuntimeContextID, RuntimeContextID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeRuntimeContext, model.DeleteErrorAssignmentState) formationAssignments := []*model.FormationAssignment{fa1, fa2, fa3, fa4} + assignmentOperationWithAssignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Assign, model.AssignObject) + assignmentOperationWithUnassignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Unassign, model.UnassignObject) + formationAssignmentsInDeletingState := cloneFormationAssignments(formationAssignments) setAssignmentsToState(model.DeletingAssignmentState, formationAssignmentsInDeletingState...) @@ -148,6 +151,10 @@ func TestServiceFinalizeDraftFormation(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -222,6 +229,10 @@ func TestServiceFinalizeDraftFormation(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() diff --git a/components/director/internal/domain/formation/fixtures_test.go b/components/director/internal/domain/formation/fixtures_test.go index 15976235ae..cdbc34a3bf 100644 --- a/components/director/internal/domain/formation/fixtures_test.go +++ b/components/director/internal/domain/formation/fixtures_test.go @@ -104,10 +104,7 @@ const ( FormationAssignmentState = "FormationAssignmentState" // Assignment Operation constants - AssignmentOperationID = "AssignmentOperationID" - AssignmentOperation2ID = "AssignmentOperation2ID" - AssignmentOperation3ID = "AssignmentOperation3ID" - AssignmentOperation4ID = "AssignmentOperation4ID" + AssignmentOperationID = "AssignmentOperationID" // Other constants ErrMsg = "some error" @@ -1595,6 +1592,18 @@ func fixDetailsForNotificationStatusReturned(formationType string, operation mod } } +func fixAssignmentOperationModelWithTypeAndTrigger(opType model.AssignmentOperationType, opTrigger model.OperationTrigger) *model.AssignmentOperation { + return &model.AssignmentOperation{ + ID: AssignmentOperationID, + Type: opType, + FormationAssignmentID: AssignmentOperationID, + FormationID: FormationID, + TriggeredBy: opTrigger, + StartedAtTimestamp: &defaultTime, + FinishedAtTimestamp: &defaultTime, + } +} + func ctxWithTenantAndLoggerMatcher() interface{} { return mock.MatchedBy(func(ctx context.Context) bool { return tenantMatcher(ctx) && loggerMatcher(ctx) diff --git a/components/director/internal/domain/formation/service.go b/components/director/internal/domain/formation/service.go index 5ce62e163d..0860b0ddf2 100644 --- a/components/director/internal/domain/formation/service.go +++ b/components/director/internal/domain/formation/service.go @@ -1329,11 +1329,11 @@ func (s *service) resynchronizeFormationAssignmentNotifications(ctx context.Cont logger := log.C(ctx).WithField(log.FieldFormationAssignmentID, fa.ID) ctx = log.ContextWithLogger(ctx, logger) - latestAssignmentOperation, err := s.assignmentOperationService.GetLatestOperation(ctx, fa.ID, fa.FormationID) + latestAssignmentOperation, err := s.assignmentOperationService.GetLatestOperation(ctxWithTransact, fa.ID, fa.FormationID) if err != nil { return err } - formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(latestAssignmentOperation.Type) // todo::: placeholder + formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(latestAssignmentOperation.Type) var notificationForReverseFA *webhookclient.FormationAssignmentNotificationRequest notificationForFA, err := s.formationAssignmentNotificationService.GenerateFormationAssignmentNotification(ctxWithTransact, fa, formationOperation) diff --git a/components/director/internal/domain/formation/service_test.go b/components/director/internal/domain/formation/service_test.go index c6f4b886ad..ca084de801 100644 --- a/components/director/internal/domain/formation/service_test.go +++ b/components/director/internal/domain/formation/service_test.go @@ -3298,10 +3298,8 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { assignmentOperation4 := mock.MatchedBy(func(op *model.AssignmentOperationInput) bool { return op.Type == model.Assign && op.FormationAssignmentID == "id4" && op.FormationID == FormationID && op.TriggeredBy == model.ResetAssignment }) - //op1 := fixAssignmentOperationModel(AssignmentOperationID, "id1", model.Assign, model.ResyncAssignment) - //op2 := fixAssignmentOperationModel(AssignmentOperationID, "id2", model.Assign, model.ResyncAssignment) - //op3 := fixAssignmentOperationModel(AssignmentOperationID, "id3", model.Assign, model.ResyncAssignment) - //op4 := fixAssignmentOperationModel(AssignmentOperationID, "id4", model.Assign, model.ResyncAssignment) + assignmentOperationWithAssignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Assign, model.AssignObject) + assignmentOperationWithUnassignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Unassign, model.UnassignObject) formationAssignmentsInDeletingState := cloneFormationAssignments(formationAssignments) setAssignmentsToState(model.DeletingAssignmentState, formationAssignmentsInDeletingState...) @@ -3437,6 +3435,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3484,6 +3486,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3544,6 +3550,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3598,6 +3608,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { ExpectedErrMessage: testErr.Error(), AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3645,6 +3659,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3693,6 +3711,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3743,6 +3765,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3798,6 +3824,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3847,6 +3877,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3886,6 +3920,11 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { repo.On("Get", ctxWithTenantAndLoggerMatcher(), FormationID, TntInternalID).Return(testFormation, nil).Once() return repo }, + AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, ExpectedErrMessage: testErr.Error(), }, { @@ -3921,6 +3960,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -3964,6 +4007,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -4013,6 +4060,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -4021,6 +4072,29 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, ExpectedErrMessage: testErr.Error(), }, + { + Name: "returns error when failing to get latest operation", + FormationAssignments: formationAssignments, + TxFn: func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner) { + return txGen.ThatDoesntExpectCommit() + }, + FormationAssignmentServiceFn: func() *automock.FormationAssignmentService { + svc := &automock.FormationAssignmentService{} + svc.On("GetAssignmentsForFormationWithStates", txtest.CtxWithDBMatcher(), TntInternalID, FormationID, allStates).Return(formationAssignments, nil).Once() + return svc + }, + FormationRepositoryFn: func() *automock.FormationRepository { + repo := &automock.FormationRepository{} + repo.On("Get", ctxWithTenantAndLoggerMatcher(), FormationID, TntInternalID).Return(testFormation, nil).Once() + return repo + }, + AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(nil, testErr).Once() + return svc + }, + ExpectedErrMessage: testErr.Error(), + }, { Name: "returns error when failing to update operation", FormationAssignments: formationAssignments, @@ -4048,6 +4122,7 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(testErr).Once() return svc }, @@ -4074,6 +4149,11 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { repo.On("Get", ctxWithTenantAndLoggerMatcher(), FormationID, TntInternalID).Return(testFormation, nil).Once() return repo }, + AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, ExpectedErrMessage: testErr.Error(), }, { @@ -4098,6 +4178,11 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { repo.On("Get", ctxWithTenantAndLoggerMatcher(), FormationID, TntInternalID).Return(testFormation, nil).Once() return repo }, + AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, ExpectedErrMessage: testErr.Error(), }, { @@ -4119,6 +4204,11 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { repo.On("Get", ctxWithTenantAndLoggerMatcher(), FormationID, TntInternalID).Return(testFormation, nil).Once() return repo }, + AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, ExpectedErrMessage: testErr.Error(), }, { @@ -4212,6 +4302,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { }, AssignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithUnassignType, nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResyncAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResyncAssignment).Return(nil).Once() @@ -4593,6 +4687,11 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperation3).Return("", nil).Once() svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperation4).Return("", nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), formationAssignments[3].ID, FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[0].ID, FormationID, model.ResetAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[1].ID, FormationID, model.ResetAssignment).Return(nil).Once() svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignments[2].ID, FormationID, model.ResetAssignment).Return(nil).Once() diff --git a/components/director/internal/domain/formationassignment/converter.go b/components/director/internal/domain/formationassignment/converter.go index 5d11cf5cfa..187f4061de 100644 --- a/components/director/internal/domain/formationassignment/converter.go +++ b/components/director/internal/domain/formationassignment/converter.go @@ -50,15 +50,13 @@ func (c *converter) ToGraphQL(in *model.FormationAssignment) (*graphql.Formation value = configurationStrValue } - state := in.GetNotificationState() - return &graphql.FormationAssignment{ ID: in.ID, Source: in.Source, SourceType: graphql.FormationAssignmentType(in.SourceType), Target: in.Target, TargetType: graphql.FormationAssignmentType(in.TargetType), - State: state, + State: in.State, Value: value, Configuration: configurationStrValue, Error: errorStrValue, diff --git a/components/director/internal/domain/formationassignment/converter_test.go b/components/director/internal/domain/formationassignment/converter_test.go index aa5ca53115..cf35554496 100644 --- a/components/director/internal/domain/formationassignment/converter_test.go +++ b/components/director/internal/domain/formationassignment/converter_test.go @@ -33,16 +33,6 @@ func TestToGraphQL(t *testing.T) { Input: fixFormationAssignmentModelWithConfigAndError(TestConfigValueRawJSON, TestErrorValueRawJSON), Expected: fixFormationAssignmentGQLModelWithConfigAndError(&TestConfigValueStr, &TestErrorValueStr), }, - { - Name: "Success when assignment is in INSTANCE_CREATOR_DELETING should return DELETING state", - Input: fixFormationAssignmentModelWithState(string(model.InstanceCreatorDeletingAssignmentState)), - Expected: fixFormationAssignmentGQLModelWithState(string(model.DeletingAssignmentState)), - }, - { - Name: "Success when assignment is in INSTANCE_CREATOR_DELETE_ERROR should return DELETE_ERROR state", - Input: fixFormationAssignmentModelWithState(string(model.InstanceCreatorDeleteErrorAssignmentState)), - Expected: fixFormationAssignmentGQLModelWithState(string(model.DeleteErrorAssignmentState)), - }, { Name: "Success when input is nil", Input: nil, diff --git a/components/director/internal/domain/formationassignment/fixtures_test.go b/components/director/internal/domain/formationassignment/fixtures_test.go index 63978de62a..d73329f07a 100644 --- a/components/director/internal/domain/formationassignment/fixtures_test.go +++ b/components/director/internal/domain/formationassignment/fixtures_test.go @@ -183,7 +183,7 @@ func fixFormationAssignmentModelWithConfigAndError(configValue, errorValue json. } } -func fixFormationAssignmentModelWithState(state string) *model.FormationAssignment { +func fixFormationAssignmentModelWithState(state string) *model.FormationAssignment { // todo::: can be deleted ? return &model.FormationAssignment{ ID: TestID, FormationID: TestFormationID, diff --git a/components/director/internal/domain/formationassignment/notifications.go b/components/director/internal/domain/formationassignment/notifications.go index b2327a1677..a49e38b2d3 100644 --- a/components/director/internal/domain/formationassignment/notifications.go +++ b/components/director/internal/domain/formationassignment/notifications.go @@ -618,7 +618,6 @@ func convertFormationAssignmentFromModel(formationAssignment *model.FormationAss if formationAssignment == nil { return &webhook.FormationAssignment{} } - state := formationAssignment.GetNotificationState() return &webhook.FormationAssignment{ ID: formationAssignment.ID, FormationID: formationAssignment.FormationID, @@ -627,7 +626,7 @@ func convertFormationAssignmentFromModel(formationAssignment *model.FormationAss SourceType: formationAssignment.SourceType, Target: formationAssignment.Target, TargetType: formationAssignment.TargetType, - State: state, + State: formationAssignment.State, Value: str.StringifyJSONRawMessage(formationAssignment.Value), Error: str.StringifyJSONRawMessage(formationAssignment.Error), } diff --git a/components/director/internal/domain/formationassignment/service_test.go b/components/director/internal/domain/formationassignment/service_test.go index 22cd87dcfe..6fd6ca09ec 100644 --- a/components/director/internal/domain/formationassignment/service_test.go +++ b/components/director/internal/domain/formationassignment/service_test.go @@ -51,7 +51,7 @@ var ( createReadyAssignmentState = string(model.CreateReadyFormationAssignmentState) initialAssignmentState = string(model.InitialAssignmentState) deleteErrorAssignmentState = string(model.DeleteErrorAssignmentState) - instanceCreatorDeleteErrorAssignmentState = string(model.InstanceCreatorDeleteErrorAssignmentState) + instanceCreatorDeleteErrorAssignmentState = string(model.DeleteErrorAssignmentState) // todo::: can be deleted invalidState = "invalidState" formation = &model.Formation{ diff --git a/components/director/internal/domain/formationassignment/status_service_test.go b/components/director/internal/domain/formationassignment/status_service_test.go index 339118f3d3..13dc042e31 100644 --- a/components/director/internal/domain/formationassignment/status_service_test.go +++ b/components/director/internal/domain/formationassignment/status_service_test.go @@ -24,17 +24,16 @@ var ( assignmentConfigOld = json.RawMessage(`{"old": "config"}`) assignmentError = json.RawMessage(`{"error":{"message":"error from report","errorCode":2}}`) - assignmentWithStateAndConfig = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.ReadyAssignmentState), assignmentConfig, nil) - assignmentWithoutConfig = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.ReadyAssignmentState), nil, nil) - assignmentWithStateAndOldConfig = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.ConfigPendingAssignmentState), assignmentConfigOld, nil) - assignmentWithConfigAndError = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.DeleteErrorAssignmentState), assignmentConfig, assignmentError) - assignmentWithConfigAndInstanceCreatorError = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.InstanceCreatorDeleteErrorAssignmentState), assignmentConfig, assignmentError) - notificationStatusReport = fixNotificationStatusReport() - statusReportWithConfig = fixNotificationStatusReportWithStateAndConfig(assignmentConfig, readyAssignmentState) - statusReportWithoutConfigAndError = fixNotificationStatusReportWithStateAndConfig(nil, readyAssignmentState) - statusReportWithConfigConsideredEmpty = fixNotificationStatusReportWithStateAndConfig(json.RawMessage("{}"), readyAssignmentState) - statusReportWithError = fixNotificationStatusReportWithStateAndError(deleteErrorAssignmentState, "error from report") - statusReportWithInstanceCreatorError = fixNotificationStatusReportWithStateAndError(instanceCreatorDeleteErrorAssignmentState, "error from report") + assignmentWithStateAndConfig = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.ReadyAssignmentState), assignmentConfig, nil) + assignmentWithoutConfig = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.ReadyAssignmentState), nil, nil) + assignmentWithStateAndOldConfig = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.ConfigPendingAssignmentState), assignmentConfigOld, nil) + assignmentWithConfigAndError = fixFormationAssignmentModelWithParameters(TestID, TestFormationID, TestTenantID, TestSource, TestTarget, TestSourceType, TestTargetType, string(model.DeleteErrorAssignmentState), assignmentConfig, assignmentError) + notificationStatusReport = fixNotificationStatusReport() + statusReportWithConfig = fixNotificationStatusReportWithStateAndConfig(assignmentConfig, readyAssignmentState) + statusReportWithoutConfigAndError = fixNotificationStatusReportWithStateAndConfig(nil, readyAssignmentState) + statusReportWithConfigConsideredEmpty = fixNotificationStatusReportWithStateAndConfig(json.RawMessage("{}"), readyAssignmentState) + statusReportWithError = fixNotificationStatusReportWithStateAndError(deleteErrorAssignmentState, "error from report") + statusReportWithInstanceCreatorError = fixNotificationStatusReportWithStateAndError(deleteErrorAssignmentState, "error from report") ) func TestStatusService_UpdateWithConstraints(t *testing.T) { @@ -168,7 +167,7 @@ func TestStatusService_UpdateWithConstraints(t *testing.T) { FormationAssignment: assignmentWithStateAndConfig, FormationAssignmentRepo: func() *automock.FormationAssignmentRepository { repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctxWithTenant, assignmentWithConfigAndInstanceCreatorError).Return(nil).Once() + repo.On("Update", ctxWithTenant, assignmentWithConfigAndError).Return(nil).Once() return repo }, ConstraintEngine: func() *automock.ConstraintEngine { diff --git a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go index fecd3238e7..d6cab01cc6 100644 --- a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go +++ b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go @@ -57,7 +57,7 @@ func (e *ConstraintEngine) AsynchronousFlowControlOperator(ctx context.Context, return false, err } - if latestAssignmentOperation.Type == model.InstanceCreatorUnassign { // todo::: placeholder + if latestAssignmentOperation.Type == model.InstanceCreatorUnassign { log.C(ctx).Infof("Tenant mapping participant processing unassign notification has alredy finished, redirecting notification for assignment %q with state %q to instance creator", formationAssignment.ID, formationAssignment.State) ri.ShouldRedirect = true return e.RedirectNotification(ctx, &ri.RedirectNotificationInput) @@ -107,13 +107,9 @@ func (e *ConstraintEngine) AsynchronousFlowControlOperator(ctx context.Context, log.C(ctx).Warnf(errors.Wrapf(err, "Reverse assignment not found").Error()) } - log.C(ctx).Infof("Tenant mapping participant finished processing unassign notification successfully for assignment with ID %q, will create new %q Assignment Operation", formationAssignment.ID, model.InstanceCreatorUnassign) // todo::: log that you will create the IC_UNASSIGN OP - statusReport.State = string(model.DeletingAssignmentState) // todo::: set to DELETING state so that in DeleteWithConstraints we don't delete the FA - //if err = e.formationAssignmentRepo.Update(ctx, formationAssignment); err != nil { - // return false, errors.Wrapf(err, "while updating formation assignment with ID %q", formationAssignment.ID) - //} // todo:::delete + log.C(ctx).Infof("Tenant mapping participant finished processing unassign notification successfully for assignment with ID %q, will create new %q Assignment Operation", formationAssignment.ID, model.InstanceCreatorUnassign) + statusReport.State = string(model.DeletingAssignmentState) // set to DELETING state so that in CleanupFormationAssignment -> DeleteWithConstraints we don't delete the FA - // todo::: create INSTANCE_CREATOR_UNASSIGN operation _, err = e.assignmentOperationService.Create(ctx, &model.AssignmentOperationInput{ Type: model.InstanceCreatorUnassign, FormationAssignmentID: formationAssignment.ID, @@ -137,23 +133,17 @@ func (e *ConstraintEngine) AsynchronousFlowControlOperator(ctx context.Context, return true, nil } - if formationAssignment.State == string(model.DeletingAssignmentState) && statusReport.State == string(model.DeleteErrorAssignmentState) { - return true, nil - } - latestAssignmentOperation, err := e.assignmentOperationService.GetLatestOperation(ctx, formationAssignment.ID, formationAssignment.FormationID) if err != nil { return false, err } - if latestAssignmentOperation.Type == model.InstanceCreatorUnassign && statusReport.State == string(model.ReadyAssignmentState) { // todo::: instead of checking the FA state, get its latest operation and check if it's INSTANCE_CREATOR_UNASSIGN - log.C(ctx).Infof("Instance creator reported %q, proceeding with deletion of formation assignment with ID %q", statusReport.State, formationAssignment.ID) + if latestAssignmentOperation.Type == model.InstanceCreatorUnassign && statusReport.State == string(model.DeleteErrorFormationState) { + log.C(ctx).Infof("Instance creator reported %q for formation assignment with ID %q", statusReport.State, formationAssignment.ID) return true, nil } - if latestAssignmentOperation.Type == model.InstanceCreatorUnassign && statusReport.State == string(model.DeleteErrorFormationState) { // todo::: instead of checking the FA state, get its latest operation and check if it's INSTANCE_CREATOR_UNASSIGN - //statusReport.State = string(model.InstanceCreatorDeleteErrorAssignmentState) // todo::: delete - // //todo::: create INSTANCE_CREATOR_UNASSIGN operation ; UPDATE: skip - log.C(ctx).Infof("Instance creator reported %q for formation assignment with ID %q", statusReport.State, formationAssignment.ID) + + if formationAssignment.State == string(model.DeletingAssignmentState) && statusReport.State == string(model.DeleteErrorAssignmentState) { return true, nil } } diff --git a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go index 4f22a55bc3..872ea3c024 100644 --- a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go @@ -32,6 +32,9 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { inputForSendNotificationUnassign := fixAsynchronousFlowControlOperatorInputWithAssignmentAndReverseFAMemoryAddress(model.UnassignFormation, fixWebhook(), preSendNotificationLocation) inputForPreAssign := fixAsynchronousFlowControlOperatorInputWithAssignmentAndReverseFAMemoryAddress(model.UnassignFormation, fixWebhook(), preAssignFormationLocation) + assignmentOperationWithUnassignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Unassign, model.UnassignObject) + assignmentOperationWithInstanceCreatorUnassignType := fixAssignmentOperationModelWithTypeAndTrigger(model.InstanceCreatorUnassign, model.UnassignObject) + testAssignmentPair := &formationassignment.AssignmentMappingPairWithOperation{ AssignmentMappingPair: &formationassignment.AssignmentMappingPair{ AssignmentReqMapping: nil, @@ -50,6 +53,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { FormationAssignmentRepository func() *automock.FormationAssignmentRepository FormationAssignmentService func() *automock.FormationAssignmentService FormationAssignmentNotificationService func() *automock.FormationAssignmentNotificationService + AssignmentOperationService func() *automock.AssignmentOperationService ExpectedResult bool ExpectedFormationAssignmentState string ExpectedStatusReportState string @@ -68,29 +72,30 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { }, // Unassign during SendNotification { - Name: "Success when sending notification and state is INSTANCE_CREATOR_DELETING state", - Input: inputForSendNotificationUnassign, - Context: ctxWithCertConsumer, - Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), - ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), - ExpectShouldRedirect: true, - ExpectedResult: true, - }, - { - Name: "Success when sending notification and state is INSTANCE_CREATOR_DELETE_ERROR state", - Input: inputForSendNotificationUnassign, - Context: ctxWithCertConsumer, - Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeleteErrorAssignmentState), - ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + Name: "Success when sending notification and latest assignment operation is INSTANCE_CREATOR_UNASSIGN", + Input: inputForSendNotificationUnassign, + Context: ctxWithCertConsumer, + Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", ctxWithCertConsumer, formationAssignmentID, formationID).Return(assignmentOperationWithInstanceCreatorUnassignType, nil).Once() + return svc + }, ExpectShouldRedirect: true, ExpectedResult: true, }, { - Name: "Success when sending notification and state is READY state", - Input: inputForSendNotificationUnassign, - Context: ctxWithCertConsumer, - Assignment: fixFormationAssignmentWithState(model.ReadyAssignmentState), - ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + Name: "Success when sending notification and state is READY state", + Input: inputForSendNotificationUnassign, + Context: ctxWithCertConsumer, + Assignment: fixFormationAssignmentWithState(model.ReadyAssignmentState), + ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", ctxWithCertConsumer, formationAssignmentID, formationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, ExpectShouldRedirect: false, ExpectedResult: true, }, @@ -102,6 +107,19 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { StatusReport: fixNotificationStatusReportWithStateAndConfig(string(model.ReadyAssignmentState), configWithDifferentStructure), ExpectedErrorMsg: "The join point details' assignment memory address cannot be 0", }, + { + Name: "Error when sending notification and getting latest assignment operation fails", + Input: inputForSendNotificationUnassign, + Context: ctxWithCertConsumer, + Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", ctxWithCertConsumer, formationAssignmentID, formationID).Return(nil, testErr).Once() + return svc + }, + ExpectedErrorMsg: testErr.Error(), + }, // Assign during PreStatusReturned { Name: "Error when formation assignment config is invalid", @@ -154,14 +172,9 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), - ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), - ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), - FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { - repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() - return repo - }, + ExpectedStatusReportState: string(model.DeletingAssignmentState), FormationAssignmentService: func() *automock.FormationAssignmentService { svc := &automock.FormationAssignmentService{} svc.On("CleanupFormationAssignment", ctxWithCertConsumer, testAssignmentPair).Return(false, nil) @@ -169,9 +182,14 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { }, FormationAssignmentNotificationService: func() *automock.FormationAssignmentNotificationService { notificationSvc := &automock.FormationAssignmentNotificationService{} - notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() + notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.DeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() return notificationSvc }, + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("Create", ctxWithCertConsumer, fixAssignmentOperationInputWithTypeAndTrigger(model.InstanceCreatorUnassign, model.UnassignObject)).Return(assignmentOperationID, nil).Once() + return svc + }, ExpectedResult: true, }, { @@ -198,19 +216,30 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { ExpectedErrorMsg: "while fetching consumer info from context", }, { - Name: "Error when cleanup formation assignment fails", + Name: "Error when creating assignment operation fails", Input: inputForNotificationStatusReturnedUnassign, Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), - ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), - ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), - FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { - repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() - return repo + ExpectedStatusReportState: string(model.DeletingAssignmentState), + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("Create", ctxWithCertConsumer, fixAssignmentOperationInputWithTypeAndTrigger(model.InstanceCreatorUnassign, model.UnassignObject)).Return("", testErr).Once() + return svc }, + ExpectedErrorMsg: testErr.Error(), + }, + { + Name: "Error when cleanup formation assignment fails", + Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, + Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), + StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), + ExpectedStatusReportState: string(model.DeletingAssignmentState), FormationAssignmentService: func() *automock.FormationAssignmentService { svc := &automock.FormationAssignmentService{} svc.On("CleanupFormationAssignment", ctxWithCertConsumer, testAssignmentPair).Return(false, testErr) @@ -218,9 +247,14 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { }, FormationAssignmentNotificationService: func() *automock.FormationAssignmentNotificationService { notificationSvc := &automock.FormationAssignmentNotificationService{} - notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() + notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.DeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() return notificationSvc }, + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("Create", ctxWithCertConsumer, fixAssignmentOperationInputWithTypeAndTrigger(model.InstanceCreatorUnassign, model.UnassignObject)).Return(assignmentOperationID, nil).Once() + return svc + }, ExpectedResult: false, ExpectedErrorMsg: testErr.Error(), }, @@ -230,86 +264,74 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), - ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), - ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), - FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { - repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() - return repo - }, + ExpectedStatusReportState: string(model.DeletingAssignmentState), FormationAssignmentNotificationService: func() *automock.FormationAssignmentNotificationService { notificationSvc := &automock.FormationAssignmentNotificationService{} - notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(nil, testErr).Once() + notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.DeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(nil, testErr).Once() return notificationSvc }, + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("Create", ctxWithCertConsumer, fixAssignmentOperationInputWithTypeAndTrigger(model.InstanceCreatorUnassign, model.UnassignObject)).Return(assignmentOperationID, nil).Once() + return svc + }, ExpectedResult: false, ExpectedErrorMsg: testErr.Error(), }, { - Name: "Error during formation assignment update to INSTANCE_CREATOR_DELETING state", + Name: "Error when getting latest assignment operation fails", Input: inputForNotificationStatusReturnedUnassign, Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), - ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), - StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), - ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), - FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { - repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(testErr).Once() - return repo + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), + StatusReport: fixNotificationStatusReportWithState(model.DeleteErrorAssignmentState), + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", ctxWithCertConsumer, formationAssignmentID, formationID).Return(nil, testErr).Once() + return svc }, - ExpectedResult: false, ExpectedErrorMsg: testErr.Error(), }, { - Name: "Success when transitioning from DELETING to DELETE_ERROR", + Name: "Success when transitioning from INSTANCE_CREATOR_UNASSIGN latest operation to DELETE_ERROR", Input: inputForNotificationStatusReturnedUnassign, Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), StatusReport: fixNotificationStatusReportWithState(model.DeleteErrorAssignmentState), - ExpectedStatusReportState: string(model.DeleteErrorAssignmentState), - ExpectedResult: true, + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", ctxWithCertConsumer, formationAssignmentID, formationID).Return(assignmentOperationWithInstanceCreatorUnassignType, nil).Once() + return svc + }, + ExpectedStatusReportState: string(model.DeleteErrorAssignmentState), + ExpectedResult: true, }, { - Name: "Success when transitioning from INSTANCE_CREATOR_DELETING to DELETE_ERROR", + Name: "Success when transitioning from DELETING to DELETE_ERROR", Input: inputForNotificationStatusReturnedUnassign, Context: ctxWithCertConsumer, - Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), + Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), - ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), StatusReport: fixNotificationStatusReportWithState(model.DeleteErrorAssignmentState), - ExpectedStatusReportState: string(model.InstanceCreatorDeleteErrorAssignmentState), - ExpectedResult: true, - }, - { - Name: "Success when transitioning from INSTANCE_CREATOR_DELETING to READY", - Input: inputForNotificationStatusReturnedUnassign, - Context: ctxWithCertConsumer, - Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), - ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), - ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), - StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), - ExpectedStatusReportState: string(model.ReadyAssignmentState), - ExpectedResult: true, - }, - { - Name: "Success when transitioning from INSTANCE_CREATOR_DELETING to READY without reverse assignment", - Input: inputForNotificationStatusReturnedUnassign, - Context: ctxWithCertConsumer, - Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), - StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), - ExpectedStatusReportState: string(model.ReadyAssignmentState), + AssignmentOperationService: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", ctxWithCertConsumer, formationAssignmentID, formationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, + ExpectedStatusReportState: string(model.DeleteErrorAssignmentState), ExpectedResult: true, }, { Name: "Error when retrieving status report pointer fails", Input: inputForNotificationStatusReturnedUnassign, Context: ctxWithCertConsumer, - Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), + Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedErrorMsg: "The join point details' notification status report memory address cannot be 0", }, @@ -340,8 +362,12 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { if testCase.FormationAssignmentNotificationService != nil { formationAssignmentNotificationService = testCase.FormationAssignmentNotificationService() } + assignmentOperationService := &automock.AssignmentOperationService{} + if testCase.AssignmentOperationService != nil { + assignmentOperationService = testCase.AssignmentOperationService() + } - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, formationAssignmentRepo, formationAssignmentService, formationAssignmentNotificationService, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, formationAssignmentRepo, formationAssignmentService, formationAssignmentNotificationService, assignmentOperationService, runtimeType, applicationType) inputClone := cloneAsynchronousFlowControlOperatorInput(testCase.Input) if testCase.Assignment != nil { @@ -371,14 +397,14 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { assert.NoError(t, err) } - mock.AssertExpectationsForObjects(t, formationAssignmentRepo, formationAssignmentService, formationAssignmentNotificationService) + mock.AssertExpectationsForObjects(t, formationAssignmentRepo, formationAssignmentService, formationAssignmentNotificationService, assignmentOperationService) }) } t.Run("Error when incorrect input is provided", func(t *testing.T) { // GIVEN - engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) + engine := operators.NewConstraintEngine(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, runtimeType, applicationType) // WHEN input := "wrong input" diff --git a/components/director/internal/domain/formationconstraint/operators/automock/assignment_operation_service.go b/components/director/internal/domain/formationconstraint/operators/automock/assignment_operation_service.go new file mode 100644 index 0000000000..31796f8fac --- /dev/null +++ b/components/director/internal/domain/formationconstraint/operators/automock/assignment_operation_service.go @@ -0,0 +1,79 @@ +// Code generated by mockery. DO NOT EDIT. + +package automock + +import ( + context "context" + + model "github.com/kyma-incubator/compass/components/director/internal/model" + mock "github.com/stretchr/testify/mock" +) + +// AssignmentOperationService is an autogenerated mock type for the assignmentOperationService type +type AssignmentOperationService struct { + mock.Mock +} + +// Create provides a mock function with given fields: ctx, in +func (_m *AssignmentOperationService) Create(ctx context.Context, in *model.AssignmentOperationInput) (string, error) { + ret := _m.Called(ctx, in) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *model.AssignmentOperationInput) (string, error)); ok { + return rf(ctx, in) + } + if rf, ok := ret.Get(0).(func(context.Context, *model.AssignmentOperationInput) string); ok { + r0 = rf(ctx, in) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, *model.AssignmentOperationInput) error); ok { + r1 = rf(ctx, in) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLatestOperation provides a mock function with given fields: ctx, assignmentID, formationID +func (_m *AssignmentOperationService) GetLatestOperation(ctx context.Context, assignmentID string, formationID string) (*model.AssignmentOperation, error) { + ret := _m.Called(ctx, assignmentID, formationID) + + var r0 *model.AssignmentOperation + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.AssignmentOperation, error)); ok { + return rf(ctx, assignmentID, formationID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *model.AssignmentOperation); ok { + r0 = rf(ctx, assignmentID, formationID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AssignmentOperation) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, assignmentID, formationID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewAssignmentOperationService creates a new instance of AssignmentOperationService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAssignmentOperationService(t interface { + mock.TestingT + Cleanup(func()) +}) *AssignmentOperationService { + mock := &AssignmentOperationService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/components/director/internal/domain/formationconstraint/operators/fixtures_test.go b/components/director/internal/domain/formationconstraint/operators/fixtures_test.go index fbc53fb39e..e1d2b4cca6 100644 --- a/components/director/internal/domain/formationconstraint/operators/fixtures_test.go +++ b/components/director/internal/domain/formationconstraint/operators/fixtures_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "time" "github.com/kyma-incubator/compass/components/director/internal/domain/statusreport" @@ -28,6 +29,8 @@ const ( runtimeID = "c66341c4-ca3a-11ed-afa1-0242ac120564" runtimeCtxID = "f7156h4-ca3a-11ed-afa1-0242ac121237" formationAssignmentID = "c54341c4-ca3a-11ed-afa1-0242ac120564" + assignmentOperationID = "3a711086-3cac-4253-97e9-6c0417f5cc67" + formationID = "b60457ed-a1e2-4a99-8c78-8e685ceb641c" formationTemplateID = "b87631c4-ca3a-11ed-afa1-0242ac120002" otherFormationTemplateID = "b05731c4-ca3a-11ed-afa1-0242ac120002" webhookID = "f4aac335-8afa-421f-a5ad-da9ce7a676bc" @@ -81,6 +84,7 @@ var ( ctx = context.TODO() testErr = errors.New("test error") corrleationIDs []string + defaultTime = time.Time{} preNotificationStatusReturnedLocation = fixJoinPointLocation(model.NotificationStatusReturned, model.PreOperation) preSendNotificationLocation = fixJoinPointLocation(model.SendNotificationOperation, model.PreOperation) @@ -375,6 +379,27 @@ func setStatusReportToAsynchronousFlowControlInput(input *formationconstraintpkg input.NotificationStatusReportMemoryAddress = report.GetAddress() } +func fixAssignmentOperationModelWithTypeAndTrigger(opType model.AssignmentOperationType, opTrigger model.OperationTrigger) *model.AssignmentOperation { + return &model.AssignmentOperation{ + ID: assignmentOperationID, + Type: opType, + FormationAssignmentID: formationAssignmentID, + FormationID: formationID, + TriggeredBy: opTrigger, + StartedAtTimestamp: &defaultTime, + FinishedAtTimestamp: &defaultTime, + } +} + +func fixAssignmentOperationInputWithTypeAndTrigger(opType model.AssignmentOperationType, opTrigger model.OperationTrigger) *model.AssignmentOperationInput { + return &model.AssignmentOperationInput{ + Type: opType, + FormationAssignmentID: formationAssignmentID, + FormationID: formationID, + TriggeredBy: opTrigger, + } +} + // Destination Creator operator fixtures func fixDestinationCreatorInputWithAssignmentMemoryAddress(operation model.FormationOperation, formationAssignment *model.FormationAssignment, location formationconstraintpkg.JoinPointLocation, report *statusreport.NotificationStatusReport) *formationconstraintpkg.DestinationCreatorInput { @@ -582,8 +607,9 @@ func fixFormationAssignmentWithConfig(config json.RawMessage) *model.FormationAs func fixFormationAssignmentWithState(state model.FormationAssignmentState) *model.FormationAssignment { return &model.FormationAssignment{ - ID: formationAssignmentID, - State: string(state), + ID: formationAssignmentID, + FormationID: formationID, + State: string(state), } } diff --git a/components/director/internal/formationmapping/automock/assignment_operation_service.go b/components/director/internal/formationmapping/automock/assignment_operation_service.go index 34354e25e0..b0a6033e64 100644 --- a/components/director/internal/formationmapping/automock/assignment_operation_service.go +++ b/components/director/internal/formationmapping/automock/assignment_operation_service.go @@ -19,10 +19,6 @@ type AssignmentOperationService struct { func (_m *AssignmentOperationService) Create(ctx context.Context, in *model.AssignmentOperationInput) (string, error) { ret := _m.Called(ctx, in) - if len(ret) == 0 { - panic("no return value specified for Create") - } - var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.AssignmentOperationInput) (string, error)); ok { @@ -47,10 +43,6 @@ func (_m *AssignmentOperationService) Create(ctx context.Context, in *model.Assi func (_m *AssignmentOperationService) Finish(ctx context.Context, assignmentID string, formationID string) error { ret := _m.Called(ctx, assignmentID, formationID) - if len(ret) == 0 { - panic("no return value specified for Finish") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, assignmentID, formationID) @@ -61,6 +53,32 @@ func (_m *AssignmentOperationService) Finish(ctx context.Context, assignmentID s return r0 } +// GetLatestOperation provides a mock function with given fields: ctx, assignmentID, formationID +func (_m *AssignmentOperationService) GetLatestOperation(ctx context.Context, assignmentID string, formationID string) (*model.AssignmentOperation, error) { + ret := _m.Called(ctx, assignmentID, formationID) + + var r0 *model.AssignmentOperation + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.AssignmentOperation, error)); ok { + return rf(ctx, assignmentID, formationID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *model.AssignmentOperation); ok { + r0 = rf(ctx, assignmentID, formationID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AssignmentOperation) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, assignmentID, formationID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewAssignmentOperationService creates a new instance of AssignmentOperationService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewAssignmentOperationService(t interface { diff --git a/components/director/internal/formationmapping/fixtures_test.go b/components/director/internal/formationmapping/fixtures_test.go index 1e8aabf033..a7bf6b5b0a 100644 --- a/components/director/internal/formationmapping/fixtures_test.go +++ b/components/director/internal/formationmapping/fixtures_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "testing" + "time" "github.com/kyma-incubator/compass/components/director/internal/domain/statusreport" @@ -46,6 +47,10 @@ var ( testFormationName = "testFormationName" testFormationTemplateID = "testFormationTemplateID" + // Assignment Operation constants + assignmentOperationID = "AssignmentOperationID" + defaultTime = time.Time{} + // UCL Subaccount IDs matchingTestUCLSubaccountID = "testSubaccID" nonMatchingUCLSubaccountID = "anotherTestSubaccID" @@ -282,3 +287,15 @@ func fixNotificationStatusReportWithStateAndConfig(configuration json.RawMessage func fixNotificationStatusReportWithStateAndError(state, errorMessage string) *statusreport.NotificationStatusReport { return statusreport.NewNotificationStatusReport(nil, state, errorMessage) } + +func fixAssignmentOperationModelWithTypeAndTrigger(opType model.AssignmentOperationType, opTrigger model.OperationTrigger) *model.AssignmentOperation { + return &model.AssignmentOperation{ + ID: assignmentOperationID, + Type: opType, + FormationAssignmentID: assignmentOperationID, + FormationID: testFormationID, + TriggeredBy: opTrigger, + StartedAtTimestamp: &defaultTime, + FinishedAtTimestamp: &defaultTime, + } +} diff --git a/components/director/internal/formationmapping/handler.go b/components/director/internal/formationmapping/handler.go index adfbd48f5b..9a88eaa2e1 100644 --- a/components/director/internal/formationmapping/handler.go +++ b/components/director/internal/formationmapping/handler.go @@ -196,7 +196,7 @@ func (h *Handler) updateFormationAssignmentStatus(w http.ResponseWriter, r *http return } - formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(latestAssignmentOperation.Type) // todo::: placeholder + formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(latestAssignmentOperation.Type) notificationStatusReport := newNotificationStatusReportFromRequestBody(assignmentReqBody, fa, latestAssignmentOperation.Type) originalStateFromStatusReport := notificationStatusReport.State if !isStateSupportedForOperation(ctx, model.FormationAssignmentState(originalStateFromStatusReport), formationOperation, formation.State, reset) { @@ -803,7 +803,7 @@ func calculateState(requestBody FormationAssignmentRequestBody, fa *model.Format return fa.State } - formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(assignmentOperationType) // todo::: placeholder + formationOperation := formationassignmentpkg.DetermineFormationOperationFromLatestAssignmentOperation(assignmentOperationType) if formationOperation == model.AssignFormation { return string(model.CreateErrorAssignmentState) diff --git a/components/director/internal/formationmapping/handler_test.go b/components/director/internal/formationmapping/handler_test.go index 9a697b91a3..acde08acd9 100644 --- a/components/director/internal/formationmapping/handler_test.go +++ b/components/director/internal/formationmapping/handler_test.go @@ -45,6 +45,10 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faWithSourceAppAndTargetRuntimeWithCreateErrorState := fixFormationAssignmentModelWithStateAndConfig(testFormationAssignmentID, testFormationID, internalTntID, faSourceID, faTargetID, model.FormationAssignmentTypeApplication, model.FormationAssignmentTypeRuntime, model.CreateErrorAssignmentState, "") + assignmentOperationWithAssignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Assign, model.AssignObject) + assignmentOperationWithUnassignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Unassign, model.UnassignObject) + assignmentOperationWithInstanceCreatorUnassignType := fixAssignmentOperationModelWithTypeAndTrigger(model.InstanceCreatorUnassign, model.UnassignObject) + testFAReqMapping := formationassignment.FormationAssignmentRequestMapping{ Request: fixEmptyNotificationRequest(), FormationAssignment: faWithSourceAppAndTargetRuntime, @@ -66,7 +70,6 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { // formation assignment fixtures with UNASSIGN operation faWithSourceAppAndTargetRuntimeForUnassingOp := fixFormationAssignmentModelWithStateAndConfig(testFormationAssignmentID, testFormationID, internalTntID, faSourceID, faTargetID, model.FormationAssignmentTypeApplication, model.FormationAssignmentTypeRuntime, model.DeletingAssignmentState, testValidConfig) faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState := fixFormationAssignmentModelWithStateAndConfig(testFormationAssignmentID, testFormationID, internalTntID, faSourceID, faTargetID, model.FormationAssignmentTypeApplication, model.FormationAssignmentTypeRuntime, model.DeletingAssignmentState, "") - faWithSourceAppAndTargetRuntimeForUnassingOpWithInstanceCreatorDeletingState := fixFormationAssignmentModelWithStateAndConfig(testFormationAssignmentID, testFormationID, internalTntID, faSourceID, faTargetID, model.FormationAssignmentTypeApplication, model.FormationAssignmentTypeRuntime, model.InstanceCreatorDeletingAssignmentState, "") testFormationAssignmentsForObject := []*model.FormationAssignment{ { @@ -202,6 +205,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -241,6 +245,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -267,6 +272,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", txtest.CtxWithDBMatcher(), initialNotificationReportWithConfig, faWithInitialState, model.AssignFormation).Return(nil).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithInitialState.ID, faWithInitialState.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ Configuration: json.RawMessage(testValidConfig), }, @@ -294,6 +304,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", txtest.CtxWithDBMatcher(), initialNotificationReportWithConfig, faWithInitialState, model.AssignFormation).Return(nil).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithInitialState.ID, faWithInitialState.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ Configuration: json.RawMessage(testValidConfig), }, @@ -332,6 +347,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -340,37 +356,6 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { expectedErrOutput: "", shouldSleep: true, }, - { - name: "Error when finishing operation fails", - transactFn: txGen.ThatDoesntExpectCommit, - faServiceFn: func() *automock.FormationAssignmentService { - faSvc := &automock.FormationAssignmentService{} - faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntime, nil).Once() - return faSvc - }, - formationSvcFn: func() *automock.FormationService { - formationSvc := &automock.FormationService{} - formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() - return formationSvc - }, - faStatusSvcFn: func() *automock.FormationAssignmentStatusService { - updater := &automock.FormationAssignmentStatusService{} - updater.On("UpdateWithConstraints", txtest.CtxWithDBMatcher(), readyNotificationReportWithConfig, faWithSourceAppAndTargetRuntime, model.AssignFormation).Return(nil).Once() - return updater - }, - reqBody: fm.FormationAssignmentRequestBody{ - Configuration: json.RawMessage(testValidConfig), - }, - assignmentOperationServiceFn: func() *automock.AssignmentOperationService { - svc := &automock.AssignmentOperationService{} - svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(testErr).Once() - return svc - }, - hasURLVars: true, - expectedStatusCode: http.StatusInternalServerError, - expectedErrOutput: "An unexpected error occurred while processing the request. X-Request-Id:", - shouldSleep: true, - }, { name: "Error when transaction fails to begin", transactFn: txGen.ThatFailsOnBegin, @@ -496,6 +481,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.DeleteErrorAssignmentState, Error: configurationErr.Error(), @@ -504,6 +494,32 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { expectedStatusCode: http.StatusBadRequest, expectedErrOutput: fmt.Sprintf("An invalid state: %s is provided for %s operation", model.DeleteErrorAssignmentState, model.AssignFormation), }, + { + name: "Error when getting latest operation fails", + transactFn: txGen.ThatDoesntExpectCommit, + faServiceFn: func() *automock.FormationAssignmentService { + faSvc := &automock.FormationAssignmentService{} + faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntime, nil).Once() + return faSvc + }, + formationSvcFn: func() *automock.FormationService { + formationSvc := &automock.FormationService{} + formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() + return formationSvc + }, + reqBody: fm.FormationAssignmentRequestBody{ + Configuration: json.RawMessage(testValidConfig), + }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(nil, testErr).Once() + return svc + }, + hasURLVars: true, + expectedStatusCode: http.StatusInternalServerError, + expectedErrOutput: "An unexpected error occurred while processing the request. X-Request-Id:", + shouldSleep: true, + }, { name: "Error when request body state is DELETE_READY but operation is assign", transactFn: txGen.ThatDoesntExpectCommit, @@ -517,6 +533,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.DeleteReadyFormationAssignmentState, }, @@ -537,6 +558,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.CreateReadyFormationAssignmentState, }, @@ -558,6 +584,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithInitialState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithInitialState.ID, faWithInitialState.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ Configuration: json.RawMessage(testValidConfig), }, @@ -584,6 +615,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", txtest.CtxWithDBMatcher(), createErrorNotificationReport, faWithSourceAppAndTargetRuntime, model.AssignFormation).Return(testErr).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.CreateErrorAssignmentState, Error: configurationErr.Error(), @@ -610,6 +646,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", contextThatHasTenant(internalTntID), createErrorNotificationReport, faWithSourceAppAndTargetRuntimeWithCreateErrorState, model.AssignFormation).Return(nil).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeWithCreateErrorState.ID, faWithSourceAppAndTargetRuntimeWithCreateErrorState.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.CreateErrorAssignmentState, Error: configurationErr.Error(), @@ -636,6 +677,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", contextThatHasTenant(internalTntID), createErrorNotificationReport, faWithSourceAppAndTargetRuntimeWithCreateErrorState, model.AssignFormation).Return(nil).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeWithCreateErrorState.ID, faWithSourceAppAndTargetRuntimeWithCreateErrorState.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ Error: configurationErr.Error(), }, @@ -661,6 +707,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", contextThatHasTenant(internalTntID), createErrorNotificationReport, faWithSourceAppAndTargetRuntimeWithCreateErrorState, model.AssignFormation).Return(nil).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeWithCreateErrorState.ID, faWithSourceAppAndTargetRuntimeWithCreateErrorState.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.CreateErrorAssignmentState, Error: configurationErr.Error(), @@ -687,6 +738,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", txtest.CtxWithDBMatcher(), readyNotificationReportWithConfig, faWithSourceAppAndTargetRuntime, model.AssignFormation).Return(testErr).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -695,6 +751,38 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { expectedStatusCode: http.StatusInternalServerError, expectedErrOutput: "An unexpected error occurred while processing the request. X-Request-Id:", }, + { + name: "Error when finishing operation fails", + transactFn: txGen.ThatDoesntExpectCommit, + faServiceFn: func() *automock.FormationAssignmentService { + faSvc := &automock.FormationAssignmentService{} + faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntime, nil).Once() + return faSvc + }, + formationSvcFn: func() *automock.FormationService { + formationSvc := &automock.FormationService{} + formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() + return formationSvc + }, + faStatusSvcFn: func() *automock.FormationAssignmentStatusService { + updater := &automock.FormationAssignmentStatusService{} + updater.On("UpdateWithConstraints", txtest.CtxWithDBMatcher(), readyNotificationReportWithConfig, faWithSourceAppAndTargetRuntime, model.AssignFormation).Return(nil).Once() + return updater + }, + reqBody: fm.FormationAssignmentRequestBody{ + Configuration: json.RawMessage(testValidConfig), + }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() + svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(testErr).Once() + return svc + }, + hasURLVars: true, + expectedStatusCode: http.StatusInternalServerError, + expectedErrOutput: "An unexpected error occurred while processing the request. X-Request-Id:", + shouldSleep: true, + }, { name: "Error when committing transaction fail before go routine", transactFn: txGen.ThatFailsOnCommit, @@ -719,6 +807,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -754,6 +843,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -792,6 +882,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -825,6 +916,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -865,6 +957,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -905,6 +998,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -945,6 +1039,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -984,6 +1079,7 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntime.ID, faWithSourceAppAndTargetRuntime.FormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Finish", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(nil).Once() return svc }, @@ -1013,6 +1109,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1029,6 +1130,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntimeForUnassingOp, nil).Once() return faSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ConfigPendingAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1064,6 +1170,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", contextThatHasTenant(internalTntID), deleteErrorNotificationReport, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState, model.UnassignFormation).Return(nil).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.ID, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, hasURLVars: true, expectedStatusCode: http.StatusOK, expectedErrOutput: "", @@ -1089,6 +1200,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", contextThatHasTenant(internalTntID), deleteErrorNotificationReport, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState, model.UnassignFormation).Return(nil).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.ID, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, hasURLVars: true, expectedStatusCode: http.StatusOK, expectedErrOutput: "", @@ -1111,6 +1227,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", contextThatHasTenant(internalTntID), deleteErrorNotificationReport, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState, model.UnassignFormation).Return(testErr).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.ID, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.DeleteErrorAssignmentState, Error: configurationErr.Error(), @@ -1120,11 +1241,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { expectedErrOutput: "An unexpected error occurred while processing the request. X-Request-Id:", }, { - name: "Success when formation assignment is in INSTANCE_CREATOR_DELETING state and consumer is not instance creator does not delete assignment", + name: "Success when formation assignment is in DELETING state and consumer is not instance creator does not delete assignment", transactFn: txGen.ThatSucceeds, faServiceFn: func() *automock.FormationAssignmentService { faSvc := &automock.FormationAssignmentService{} - faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntimeForUnassingOpWithInstanceCreatorDeletingState, nil).Once() + faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState, nil).Once() return faSvc }, formationSvcFn: func() *automock.FormationService { @@ -1132,6 +1253,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.ID, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.FormationID).Return(assignmentOperationWithInstanceCreatorUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, }, @@ -1139,12 +1265,12 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { expectedStatusCode: http.StatusOK, }, { - name: "Success when formation assignment is in INSTANCE_CREATOR_DELETING state and consumer is instance creator should proceed with unassign", + name: "Success when formation assignment is in DELETING state and consumer is instance creator should proceed with unassign", transactFn: txGen.ThatSucceedsTwice, context: ctxWithInstanceCreatorConsumer, faServiceFn: func() *automock.FormationAssignmentService { faSvc := &automock.FormationAssignmentService{} - faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntimeForUnassingOpWithInstanceCreatorDeletingState, nil).Once() + faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState, nil).Once() faSvc.On("ListFormationAssignmentsForObjectID", contextThatHasTenant(internalTntID), testFormationID, faSourceID).Return(testFormationAssignmentsForObject, nil).Once() faSvc.On("ListFormationAssignmentsForObjectID", contextThatHasTenant(internalTntID), testFormationID, faTargetID).Return(testFormationAssignmentsForObject, nil).Once() return faSvc @@ -1159,6 +1285,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithoutConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.ID, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.FormationID).Return(assignmentOperationWithInstanceCreatorUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, }, @@ -1183,6 +1314,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { updater.On("UpdateWithConstraints", contextThatHasTenant(internalTntID), deleteErrorNotificationReport, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState, model.UnassignFormation).Return(testErr).Once() return updater }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.ID, faWithSourceAppAndTargetRuntimeForUnassingOpWithDeleteErrorState.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.DeleteErrorAssignmentState, Error: configurationErr.Error(), @@ -1214,6 +1350,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(testErr).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, hasURLVars: true, expectedStatusCode: http.StatusInternalServerError, expectedErrOutput: "An unexpected error occurred while processing the request. X-Request-Id:", @@ -1241,6 +1382,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(testErr).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, hasURLVars: true, expectedStatusCode: http.StatusInternalServerError, expectedErrOutput: "An unexpected error occurred while processing the request. X-Request-Id:", @@ -1265,6 +1411,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(apperrors.NewNotFoundErrorWithType(resource.FormationAssignment)).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1294,6 +1445,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1324,6 +1480,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1354,6 +1515,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1386,6 +1552,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1412,6 +1583,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1448,6 +1624,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1486,6 +1667,11 @@ func TestHandler_UpdateFormationAssignmentStatus(t *testing.T) { faStatusSvc.On("DeleteWithConstraints", contextThatHasTenant(internalTntID), testFormationAssignmentID, readyNotificationReportWithConfig).Return(nil).Once() return faStatusSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), faWithSourceAppAndTargetRuntimeForUnassingOp.ID, faWithSourceAppAndTargetRuntimeForUnassingOp.FormationID).Return(assignmentOperationWithUnassignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1615,6 +1801,7 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { assignmentOperationForReverse := mock.MatchedBy(func(op *model.AssignmentOperationInput) bool { return op.Type == model.Assign && op.FormationAssignmentID == testReverseFormationAssignmentID && op.FormationID == testFormationID && op.TriggeredBy == model.ResetAssignment }) + assignmentOperationWithAssignType := fixAssignmentOperationModelWithTypeAndTrigger(model.Assign, model.ResetAssignment) testCases := []struct { name string @@ -1661,6 +1848,7 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperation).Return("", nil).Once() svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperationForReverse).Return("", nil).Once() return svc @@ -1690,6 +1878,7 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperation).Return("", nil).Once() svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperationForReverse).Return("", testErr).Once() return svc @@ -1718,6 +1907,7 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperation).Return("", testErr).Once() return svc }, @@ -1738,6 +1928,11 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1757,6 +1952,11 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1776,6 +1976,11 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.CreateErrorAssignmentState, }, @@ -1794,6 +1999,11 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, }, @@ -1814,6 +2024,11 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1834,6 +2049,11 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), @@ -1862,6 +2082,7 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { }, assignmentOperationServiceFn: func() *automock.AssignmentOperationService { svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() svc.On("Create", txtest.CtxWithDBMatcher(), assignmentOperation).Return("", nil).Once() return svc }, @@ -1882,6 +2103,11 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() return formationSvc }, + assignmentOperationServiceFn: func() *automock.AssignmentOperationService { + svc := &automock.AssignmentOperationService{} + svc.On("GetLatestOperation", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(assignmentOperationWithAssignType, nil).Once() + return svc + }, reqBody: fm.FormationAssignmentRequestBody{ State: model.ReadyAssignmentState, Configuration: json.RawMessage(testValidConfig), diff --git a/components/director/internal/model/formation_assignment.go b/components/director/internal/model/formation_assignment.go index cac8c0ecf2..0023af4eac 100644 --- a/components/director/internal/model/formation_assignment.go +++ b/components/director/internal/model/formation_assignment.go @@ -71,16 +71,12 @@ const ( CreateErrorAssignmentState FormationAssignmentState = "CREATE_ERROR" // DeletingAssignmentState indicates that async unassign notification is sent and status report is expected from the receiver DeletingAssignmentState FormationAssignmentState = "DELETING" - // InstanceCreatorDeletingAssignmentState indicates that async unassign notification is sent and status report is expected from the receiver - InstanceCreatorDeletingAssignmentState FormationAssignmentState = "INSTANCE_CREATOR_DELETING" // DeleteErrorAssignmentState indicates that an error occurred during the deletion of the formation assignment DeleteErrorAssignmentState FormationAssignmentState = "DELETE_ERROR" // CreateReadyFormationAssignmentState indicates that the formation assignment is in a ready state and the response is for an assign notification CreateReadyFormationAssignmentState FormationAssignmentState = "CREATE_READY" // DeleteReadyFormationAssignmentState indicates that the formation assignment is in a ready state and the response is for an unassign notification DeleteReadyFormationAssignmentState FormationAssignmentState = "DELETE_READY" - // InstanceCreatorDeleteErrorAssignmentState indicates that an error occurred during the deletion of the formation assignment by the instance creator operator - InstanceCreatorDeleteErrorAssignmentState FormationAssignmentState = "INSTANCE_CREATOR_DELETE_ERROR" // NotificationRecursionDepthLimit is the maximum count of configuration exchanges during assigning an object to formation NotificationRecursionDepthLimit int = 10 ) @@ -100,10 +96,8 @@ var SupportedFormationAssignmentStates = map[string]bool{ // ResynchronizableFormationAssignmentStates is an array of supported assignment states for resynchronization var ResynchronizableFormationAssignmentStates = []string{string(InitialAssignmentState), string(DeletingAssignmentState), - string(InstanceCreatorDeletingAssignmentState), string(CreateErrorAssignmentState), - string(DeleteErrorAssignmentState), - string(InstanceCreatorDeleteErrorAssignmentState)} + string(DeleteErrorAssignmentState)} // ToModel converts FormationAssignmentInput to FormationAssignment func (i *FormationAssignmentInput) ToModel(id, tenantID string) *FormationAssignment { @@ -186,7 +180,7 @@ func (fa *FormationAssignment) GetOperation() FormationOperation { // todo::: ca // GetNotificationState returns the assignment state that is to be sent for notifications // and is exposed to the GraphQL layer -func (fa *FormationAssignment) GetNotificationState() string { +func (fa *FormationAssignment) GetNotificationState() string { // todo::: can be deleted state := fa.State if strings.HasSuffix(state, string(DeleteErrorAssignmentState)) { state = string(DeleteErrorAssignmentState) diff --git a/components/director/pkg/webhook/application_tenant_mapping.go b/components/director/pkg/webhook/application_tenant_mapping.go index 2cfbc28cd9..2d1630de76 100644 --- a/components/director/pkg/webhook/application_tenant_mapping.go +++ b/components/director/pkg/webhook/application_tenant_mapping.go @@ -62,7 +62,7 @@ func (rd *ApplicationTenantMappingInput) SetAssignment(assignment *model.Formati SourceType: assignment.SourceType, Target: assignment.Target, TargetType: assignment.TargetType, - State: assignment.GetNotificationState(), + State: assignment.State, Value: str.StringifyJSONRawMessage(assignment.Value), Error: str.StringifyJSONRawMessage(assignment.Error), } @@ -78,7 +78,7 @@ func (rd *ApplicationTenantMappingInput) SetReverseAssignment(reverseAssignment SourceType: reverseAssignment.SourceType, Target: reverseAssignment.Target, TargetType: reverseAssignment.TargetType, - State: reverseAssignment.GetNotificationState(), + State: reverseAssignment.State, Value: str.StringifyJSONRawMessage(reverseAssignment.Value), Error: str.StringifyJSONRawMessage(reverseAssignment.Error), } diff --git a/components/director/pkg/webhook/formation_configuration_change.go b/components/director/pkg/webhook/formation_configuration_change.go index 361ff428c2..7fabf516ff 100644 --- a/components/director/pkg/webhook/formation_configuration_change.go +++ b/components/director/pkg/webhook/formation_configuration_change.go @@ -135,7 +135,7 @@ func (rd *FormationConfigurationChangeInput) SetAssignment(assignment *model.For SourceType: assignment.SourceType, Target: assignment.Target, TargetType: assignment.TargetType, - State: assignment.GetNotificationState(), + State: assignment.State, Value: str.StringifyJSONRawMessage(assignment.Value), Error: str.StringifyJSONRawMessage(assignment.Error), } @@ -151,7 +151,7 @@ func (rd *FormationConfigurationChangeInput) SetReverseAssignment(reverseAssignm SourceType: reverseAssignment.SourceType, Target: reverseAssignment.Target, TargetType: reverseAssignment.TargetType, - State: reverseAssignment.GetNotificationState(), + State: reverseAssignment.State, Value: str.StringifyJSONRawMessage(reverseAssignment.Value), Error: str.StringifyJSONRawMessage(reverseAssignment.Error), } diff --git a/components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.down.sql b/components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.down.sql new file mode 100644 index 0000000000..b5ddd0496d --- /dev/null +++ b/components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.down.sql @@ -0,0 +1,30 @@ +BEGIN; + +ALTER TABLE formation_assignments DROP CONSTRAINT formation_assignments_state_check; + +ALTER TABLE formation_assignments +ADD CONSTRAINT formation_assignments_state_check +CHECK ( state IN ('INITIAL', 'READY', 'CREATE_ERROR', 'DELETING', 'DELETE_ERROR', 'CONFIG_PENDING', 'INSTANCE_CREATOR_DELETING', 'INSTANCE_CREATOR_DELETE_ERROR')); + +--- + +UPDATE formation_assignments +SET state = 'INSTANCE_CREATOR_DELETING' +WHERE id IN (SELECT formation_assignment_id FROM assignment_operations where type = 'INSTANCE_CREATOR_UNASSING') AND state = 'DELETING'; + +UPDATE formation_assignments +SET state = 'INSTANCE_CREATOR_DELETE_ERROR' +WHERE id IN (SELECT formation_assignment_id FROM assignment_operations where type = 'INSTANCE_CREATOR_UNASSING') AND state = 'DELETE_ERROR'; + +--- + +DELETE FROM assignment_operations +WHERE type = 'INSTANCE_CREATOR_UNASSIGN'; + +ALTER TABLE assignment_operations DROP CONSTRAINT assignment_operations_type_check; + +ALTER TABLE assignment_operations +ADD CONSTRAINT assignment_operations_type_check +CHECK (type IN ('ASSIGN', 'UNASSIGN')); + +COMMIT; diff --git a/components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.up.sql b/components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.up.sql new file mode 100644 index 0000000000..062f85adbb --- /dev/null +++ b/components/schema-migrator/migrations/director/20240517141734_drop_instance_creator_fa_states.up.sql @@ -0,0 +1,33 @@ +BEGIN; + +ALTER TABLE assignment_operations DROP CONSTRAINT assignment_operations_type_check; + +ALTER TABLE assignment_operations +ADD CONSTRAINT assignment_operations_type_check +CHECK (type IN ('ASSIGN', 'UNASSIGN', 'INSTANCE_CREATOR_UNASSIGN')); + +--- + +INSERT INTO assignment_operations (id, type, formation_assignment_id, formation_id, triggered_by, started_at_timestamp) +SELECT uuid_generate_v4(), 'INSTANCE_CREATOR_UNASSIGN', fa.id, fa.formation_id, 'UNASSIGN_OBJECT', CURRENT_TIMESTAMP +FROM formation_assignments fa +WHERE fa.state IN ('INSTANCE_CREATOR_DELETING', 'INSTANCE_CREATOR_DELETE_ERROR'); + +--- + +UPDATE formation_assignments +SET state = 'DELETING' +WHERE state = 'INSTANCE_CREATOR_DELETING'; + +UPDATE formation_assignments +SET state = 'DELETE_ERROR' +WHERE state = 'INSTANCE_CREATOR_DELETE_ERROR'; + +--- + +ALTER TABLE formation_assignments DROP CONSTRAINT formation_assignments_state_check; + +ALTER TABLE formation_assignments +ADD CONSTRAINT formation_assignments_state_check CHECK ( state IN ('INITIAL', 'READY', 'CREATE_ERROR', 'DELETING', 'DELETE_ERROR', 'CONFIG_PENDING')); + +COMMIT; diff --git a/tests/director/tests/notifications/application_only_notifications_new_format_test.go b/tests/director/tests/notifications/application_only_notifications_new_format_test.go index d3cfadff03..cb2c0b22b0 100644 --- a/tests/director/tests/notifications/application_only_notifications_new_format_test.go +++ b/tests/director/tests/notifications/application_only_notifications_new_format_test.go @@ -1700,14 +1700,15 @@ func TestFormationNotificationsWithApplicationOnlyParticipantsNewFormat(t *testi fixtures.NewOperation(app1ID, app1ID, "ASSIGN", "ASSIGN_OBJECT", true), fixtures.NewOperation(app2ID, app1ID, "ASSIGN", "ASSIGN_OBJECT", true), fixtures.NewOperation(app2ID, app1ID, "UNASSIGN", "UNASSIGN_OBJECT", false), + fixtures.NewOperation(app2ID, app1ID, "INSTANCE_CREATOR_UNASSIGN", "UNASSIGN_OBJECT", false), }) - faAsserter := asserters.NewFormationAssignmentAsserter(expectationsBuilder.GetExpectations(), expectationsBuilder.GetExpectedAssignmentsCount(), certSecuredGraphQLClient, tnt) + faAsyncAsserter = asserters.NewFormationAssignmentAsyncAsserter(expectationsBuilder.GetExpectations(), expectationsBuilder.GetExpectedAssignmentsCount(), certSecuredGraphQLClient, tnt) statusAsserter = asserters.NewFormationStatusAsserter(tnt, certSecuredGraphQLClient).WithCondition(graphql.FormationStatusConditionInProgress) notificationCountAsyncAsserter := asserters.NewNotificationsCountAsyncAsserter(1, unassignOperation, localTenantID, conf.ExternalServicesMockMtlsSecuredURL, certSecuredHTTPClient) notificationsUnassignAsserter := asserters.NewUnassignNotificationsAsserter(1, localTenantID, app2ID, localTenantID, appNamespace, appRegion, tnt, tntParentCustomer, "", conf.ExternalServicesMockMtlsSecuredURL, certSecuredHTTPClient) notificationCountRedirectNotificationAsyncAsserter := asserters.NewNotificationsCountAsyncAsserter(1, unassignOperation, redirectedTntID, conf.ExternalServicesMockMtlsSecuredURL, certSecuredHTTPClient) notificationsUnassignRedirectedAsserter := asserters.NewUnassignNotificationsAsserter(1, redirectedTntID, app2ID, localTenantID, appNamespace, appRegion, tnt, tntParentCustomer, "", conf.ExternalServicesMockMtlsSecuredURL, certSecuredHTTPClient).WithState(deletingAssignmentState) - op = operations.NewUnassignAppFromFormationOperation(app2ID, tnt).WithAsserters(faAsserter, statusAsserter, notificationCountAsyncAsserter, notificationsUnassignAsserter, notificationCountRedirectNotificationAsyncAsserter, notificationsUnassignRedirectedAsserter).Operation() + op = operations.NewUnassignAppFromFormationOperation(app2ID, tnt).WithAsserters(faAsyncAsserter, statusAsserter, notificationCountAsyncAsserter, notificationsUnassignAsserter, notificationCountRedirectNotificationAsyncAsserter, notificationsUnassignRedirectedAsserter).Operation() defer op.Cleanup(t, ctx, certSecuredGraphQLClient) op.Execute(t, ctx, certSecuredGraphQLClient) @@ -1727,10 +1728,12 @@ func TestFormationNotificationsWithApplicationOnlyParticipantsNewFormat(t *testi fixtures.NewOperation(app1ID, app1ID, "ASSIGN", "ASSIGN_OBJECT", true), fixtures.NewOperation(app2ID, app1ID, "ASSIGN", "ASSIGN_OBJECT", true), fixtures.NewOperation(app2ID, app1ID, "UNASSIGN", "UNASSIGN_OBJECT", false), + fixtures.NewOperation(app2ID, app1ID, "INSTANCE_CREATOR_UNASSIGN", "UNASSIGN_OBJECT", false), fixtures.NewOperation(app2ID, app1ID, "UNASSIGN", "UNASSIGN_OBJECT", false), + fixtures.NewOperation(app2ID, app1ID, "INSTANCE_CREATOR_UNASSIGN", "UNASSIGN_OBJECT", false), }) - faAsserter = asserters.NewFormationAssignmentAsserter(expectationsBuilder.GetExpectations(), expectationsBuilder.GetExpectedAssignmentsCount(), certSecuredGraphQLClient, tnt) - op = operations.NewUnassignAppFromFormationOperation(app2ID, tnt).WithAsserters(faAsserter, statusAsserter, notificationCountRedirectNotificationAsyncAsserter, notificationsUnassignRedirectedAsserter).Operation() + faAsyncAsserter = asserters.NewFormationAssignmentAsyncAsserter(expectationsBuilder.GetExpectations(), expectationsBuilder.GetExpectedAssignmentsCount(), certSecuredGraphQLClient, tnt) + op = operations.NewUnassignAppFromFormationOperation(app2ID, tnt).WithAsserters(faAsyncAsserter, statusAsserter, notificationCountRedirectNotificationAsyncAsserter, notificationsUnassignRedirectedAsserter).Operation() defer op.Cleanup(t, ctx, certSecuredGraphQLClient) op.Execute(t, ctx, certSecuredGraphQLClient) @@ -1743,7 +1746,7 @@ func TestFormationNotificationsWithApplicationOnlyParticipantsNewFormat(t *testi WithFormationAssignment(app2ID, app1ID). WithHTTPClient(appCertClient). WithExternalServicesMockMtlsSecuredURL(conf.DirectorExternalCertFAAsyncStatusURL). - WithAsserters(faAsserter, statusAsserter). + WithAsserters(faAsyncAsserter, statusAsserter). Operation() defer op.Cleanup(t, ctx, certSecuredGraphQLClient) op.Execute(t, ctx, certSecuredGraphQLClient) @@ -1764,9 +1767,9 @@ func TestFormationNotificationsWithApplicationOnlyParticipantsNewFormat(t *testi t.Logf("Unassign Application 1 from formation: %s", formationName) expectationsBuilder = mock_data.NewFAExpectationsBuilder() - faAsserter = asserters.NewFormationAssignmentAsserter(expectationsBuilder.GetExpectations(), expectationsBuilder.GetExpectedAssignmentsCount(), certSecuredGraphQLClient, tnt) + faAsyncAsserter = asserters.NewFormationAssignmentAsyncAsserter(expectationsBuilder.GetExpectations(), expectationsBuilder.GetExpectedAssignmentsCount(), certSecuredGraphQLClient, tnt) statusAsserter = asserters.NewFormationStatusAsserter(tnt, certSecuredGraphQLClient) - op = operations.NewUnassignAppFromFormationOperation(app1ID, tnt).WithAsserters(faAsserter, statusAsserter).Operation() + op = operations.NewUnassignAppFromFormationOperation(app1ID, tnt).WithAsserters(faAsyncAsserter, statusAsserter).Operation() defer op.Cleanup(t, ctx, certSecuredGraphQLClient) op.Execute(t, ctx, certSecuredGraphQLClient) }) diff --git a/tests/pkg/notifications/operations/cleanup_notifications.go b/tests/pkg/notifications/operations/cleanup_notifications.go index 8d545297b0..75ed247685 100644 --- a/tests/pkg/notifications/operations/cleanup_notifications.go +++ b/tests/pkg/notifications/operations/cleanup_notifications.go @@ -2,12 +2,11 @@ package operations import ( "context" - "net/http" - "testing" - "github.com/kyma-incubator/compass/tests/pkg/notifications/asserters" gcli "github.com/machinebox/graphql" "github.com/stretchr/testify/require" + "net/http" + "testing" ) type CleanupNotificationsOperation struct { diff --git a/tests/pkg/notifications/operations/unassign_application.go b/tests/pkg/notifications/operations/unassign_application.go index df8580b9bd..13eb6347f7 100644 --- a/tests/pkg/notifications/operations/unassign_application.go +++ b/tests/pkg/notifications/operations/unassign_application.go @@ -2,13 +2,12 @@ package operations import ( "context" - "testing" - "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-incubator/compass/tests/pkg/fixtures" "github.com/kyma-incubator/compass/tests/pkg/notifications/asserters" context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" gcli "github.com/machinebox/graphql" + "testing" ) type UnassignAppFromFormationOperation struct { From c85ed2a2897f2662f4e6b6bcffe769f41745628d Mon Sep 17 00:00:00 2001 From: Nikolay Yordanov Date: Wed, 22 May 2024 11:21:21 +0300 Subject: [PATCH 3/5] Address todos --- .../formationassignment/fixtures_test.go | 14 --------- .../formationassignment/service_test.go | 15 +++++----- .../internal/formationmapping/handler.go | 2 +- .../internal/model/formation_assignment.go | 29 ------------------- 4 files changed, 8 insertions(+), 52 deletions(-) diff --git a/components/director/internal/domain/formationassignment/fixtures_test.go b/components/director/internal/domain/formationassignment/fixtures_test.go index d73329f07a..c019339f86 100644 --- a/components/director/internal/domain/formationassignment/fixtures_test.go +++ b/components/director/internal/domain/formationassignment/fixtures_test.go @@ -183,20 +183,6 @@ func fixFormationAssignmentModelWithConfigAndError(configValue, errorValue json. } } -func fixFormationAssignmentModelWithState(state string) *model.FormationAssignment { // todo::: can be deleted ? - return &model.FormationAssignment{ - ID: TestID, - FormationID: TestFormationID, - TenantID: TestTenantID, - Source: TestSource, - SourceType: TestSourceType, - Target: TestTarget, - TargetType: TestTargetType, - State: state, - Error: nil, - } -} - func fixFormationAssignmentModelWithParameters(id, formationID, tenantID, sourceID, targetID string, sourceType, targetType model.FormationAssignmentType, state string, configValue, errorValue json.RawMessage) *model.FormationAssignment { return &model.FormationAssignment{ ID: id, diff --git a/components/director/internal/domain/formationassignment/service_test.go b/components/director/internal/domain/formationassignment/service_test.go index 6fd6ca09ec..0e55c49451 100644 --- a/components/director/internal/domain/formationassignment/service_test.go +++ b/components/director/internal/domain/formationassignment/service_test.go @@ -45,14 +45,13 @@ var ( first = 2 after = "test" - readyAssignmentState = string(model.ReadyAssignmentState) - configPendingAssignmentState = string(model.ConfigPendingAssignmentState) - createErrorAssignmentState = string(model.CreateErrorAssignmentState) - createReadyAssignmentState = string(model.CreateReadyFormationAssignmentState) - initialAssignmentState = string(model.InitialAssignmentState) - deleteErrorAssignmentState = string(model.DeleteErrorAssignmentState) - instanceCreatorDeleteErrorAssignmentState = string(model.DeleteErrorAssignmentState) // todo::: can be deleted - invalidState = "invalidState" + readyAssignmentState = string(model.ReadyAssignmentState) + configPendingAssignmentState = string(model.ConfigPendingAssignmentState) + createErrorAssignmentState = string(model.CreateErrorAssignmentState) + createReadyAssignmentState = string(model.CreateReadyFormationAssignmentState) + initialAssignmentState = string(model.InitialAssignmentState) + deleteErrorAssignmentState = string(model.DeleteErrorAssignmentState) + invalidState = "invalidState" formation = &model.Formation{ ID: TestFormationID, diff --git a/components/director/internal/formationmapping/handler.go b/components/director/internal/formationmapping/handler.go index 9a88eaa2e1..81d96ffc5f 100644 --- a/components/director/internal/formationmapping/handler.go +++ b/components/director/internal/formationmapping/handler.go @@ -533,7 +533,7 @@ func (b FormationRequestBody) Validate() error { func (h *Handler) processFormationAssignmentUnassignStatusUpdate(ctx context.Context, fa *model.FormationAssignment, statusReport *statusreport.NotificationStatusReport, latestAssignmentOperationType model.AssignmentOperationType) (bool, error) { stateFromStatusReport := model.FormationAssignmentState(statusReport.State) - if latestAssignmentOperationType == model.InstanceCreatorUnassign { // todo::: there won't be IC state anymore so think how to adapt -> maybe get fa latestOP and if it's IC_UNASSIGN enter the if + if latestAssignmentOperationType == model.InstanceCreatorUnassign { consumerInfo, err := consumer.LoadFromContext(ctx) if err != nil { return false, err diff --git a/components/director/internal/model/formation_assignment.go b/components/director/internal/model/formation_assignment.go index 0023af4eac..767b732b1a 100644 --- a/components/director/internal/model/formation_assignment.go +++ b/components/director/internal/model/formation_assignment.go @@ -7,7 +7,6 @@ import ( "unsafe" "github.com/kyma-incubator/compass/components/director/pkg/pagination" - "github.com/kyma-incubator/compass/components/director/pkg/str" ) // FormationAssignmentType describes possible source and target types @@ -149,13 +148,6 @@ func (fa *FormationAssignment) IsInProgressState() bool { return fa.isInProgressAssignState() || fa.isInProgressUnassignState() } -// IsInRegularUnassignState returns if the formation assignment is in regular unassign stage -func (fa *FormationAssignment) IsInRegularUnassignState() bool { // todo::: can be deleted - unassignOperationStates := []string{string(DeletingAssignmentState), - string(DeleteErrorAssignmentState)} - return str.ValueIn(fa.State, unassignOperationStates) -} - // SetStateToDeleting sets the state to deleting and returns if the formation assignment is updated func (fa *FormationAssignment) SetStateToDeleting() bool { if fa.isInProgressUnassignState() { @@ -169,27 +161,6 @@ func (fa *FormationAssignment) SetStateToDeleting() bool { return true } -// GetOperation returns the formation operation that is determined based on the state of the assignment -func (fa *FormationAssignment) GetOperation() FormationOperation { // todo::: can be deleted - operation := AssignFormation - if strings.HasSuffix(fa.State, string(DeleteErrorAssignmentState)) || strings.HasSuffix(fa.State, string(DeletingAssignmentState)) { - operation = UnassignFormation - } - return operation -} - -// GetNotificationState returns the assignment state that is to be sent for notifications -// and is exposed to the GraphQL layer -func (fa *FormationAssignment) GetNotificationState() string { // todo::: can be deleted - state := fa.State - if strings.HasSuffix(state, string(DeleteErrorAssignmentState)) { - state = string(DeleteErrorAssignmentState) - } else if fa.isInProgressUnassignState() { - state = string(DeletingAssignmentState) - } - return state -} - func (fa *FormationAssignment) isInProgressAssignState() bool { return fa.State == string(InitialAssignmentState) || fa.State == string(ConfigPendingAssignmentState) From 7b0ab55833c3ea0d4042cc0ea1bb3bdc9b6bf2d6 Mon Sep 17 00:00:00 2001 From: Nikolay Yordanov Date: Wed, 22 May 2024 11:43:42 +0300 Subject: [PATCH 4/5] Delete duplicate var --- .../domain/formationconstraint/operators/fixtures_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/director/internal/domain/formationconstraint/operators/fixtures_test.go b/components/director/internal/domain/formationconstraint/operators/fixtures_test.go index 1274ec547d..16971b91ff 100644 --- a/components/director/internal/domain/formationconstraint/operators/fixtures_test.go +++ b/components/director/internal/domain/formationconstraint/operators/fixtures_test.go @@ -30,7 +30,6 @@ const ( runtimeCtxID = "f7156h4-ca3a-11ed-afa1-0242ac121237" formationAssignmentID = "c54341c4-ca3a-11ed-afa1-0242ac120564" assignmentOperationID = "3a711086-3cac-4253-97e9-6c0417f5cc67" - formationID = "b60457ed-a1e2-4a99-8c78-8e685ceb641c" formationTemplateID = "b87631c4-ca3a-11ed-afa1-0242ac120002" otherFormationTemplateID = "b05731c4-ca3a-11ed-afa1-0242ac120002" formationID = "a16724q3-ba3a-13ef-a1c7-1247ac120123" From 46b49b79956d744a5e2d216b34e750d5258b077a Mon Sep 17 00:00:00 2001 From: Nikolay Yordanov Date: Wed, 22 May 2024 13:09:41 +0300 Subject: [PATCH 5/5] Add godoc --- components/director/pkg/formationassignment/util.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/director/pkg/formationassignment/util.go b/components/director/pkg/formationassignment/util.go index 5b61b1e14d..2370324a3b 100644 --- a/components/director/pkg/formationassignment/util.go +++ b/components/director/pkg/formationassignment/util.go @@ -11,6 +11,7 @@ func IsConfigEmpty(configuration string) bool { return false } +// DetermineFormationOperationFromLatestAssignmentOperation determines what's the Formation Operation based on the provided Assignment Operation type func DetermineFormationOperationFromLatestAssignmentOperation(assignmentOperationType model.AssignmentOperationType) model.FormationOperation { if assignmentOperationType == model.Assign { return model.AssignFormation