Skip to content

Commit d66bae9

Browse files
fix: handle force flag for activation in workers-offline
Signed-off-by: Alex <[email protected]>
1 parent 7813e2f commit d66bae9

File tree

2 files changed

+130
-18
lines changed

2 files changed

+130
-18
lines changed

src/aap_eda/api/views/activation.py

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -208,17 +208,30 @@ def partial_update(self, request, pk):
208208
None,
209209
description="The Activation has been deleted.",
210210
),
211+
status.HTTP_409_CONFLICT: OpenApiResponse(
212+
None,
213+
description="Activation blocked while Workers offline.",
214+
),
211215
}
212216
| RedisDependencyMixin.redis_unavailable_response(),
217+
parameters=[
218+
OpenApiParameter(
219+
name="force",
220+
description="Force delete after worker node offline",
221+
required=False,
222+
type=bool,
223+
)
224+
],
213225
)
214226
def destroy(self, request, *args, **kwargs):
215227
activation = self.get_object()
228+
force_delete = str_to_bool(
229+
request.query_params.get("force", "false"),
230+
)
216231

217-
if activation.status == ActivationStatus.WORKERS_OFFLINE:
218-
raise api_exc.Conflict(
219-
f"An Activation in state '{activation.status}' cannot be "
220-
"deleted.",
221-
)
232+
self._check_workers_offline_with_force(
233+
activation, force_delete, "Deleted"
234+
)
222235

223236
audit_log = logging_utils.generate_simple_audit_log(
224237
"Delete",
@@ -434,14 +447,33 @@ def _start(self, request, activation: models.Activation) -> Response:
434447
None,
435448
description="Activation has been disabled.",
436449
),
450+
status.HTTP_409_CONFLICT: OpenApiResponse(
451+
None,
452+
description="Activation blocked while Workers offline.",
453+
),
437454
}
438455
| RedisDependencyMixin.redis_unavailable_response(),
456+
parameters=[
457+
OpenApiParameter(
458+
name="force",
459+
description="Force disable after worker node offline",
460+
required=False,
461+
type=bool,
462+
)
463+
],
439464
)
440465
@action(methods=["post"], detail=True, rbac_action=Action.DISABLE)
441466
def disable(self, request, pk):
442467
activation = self.get_object()
443468

444469
self._check_deleting(activation)
470+
force_disable = str_to_bool(
471+
request.query_params.get("force", "false"),
472+
)
473+
474+
self._check_workers_offline_with_force(
475+
activation, force_disable, "Disabled"
476+
)
445477

446478
if activation.is_enabled:
447479
# Redis must be available in order to perform the delete.
@@ -509,19 +541,9 @@ def restart(self, request, pk):
509541
request.query_params.get("force", "false"),
510542
)
511543

512-
if (
513-
settings.DEPLOYMENT_TYPE == "podman"
514-
and activation.status == ActivationStatus.WORKERS_OFFLINE
515-
and not force_restart
516-
):
517-
# block the restart and return an error
518-
raise api_exc.Conflict(
519-
"An activation with an activation_status of 'Workers offline' "
520-
"cannot be Restarted because this will leave an orphaned "
521-
"container running on one of the 'activation-worker-node's. "
522-
"If you want to force a restart, please add the "
523-
"/?force=true query param."
524-
)
544+
self._check_workers_offline_with_force(
545+
activation, force_restart, "Restarted"
546+
)
525547
if not activation.is_enabled:
526548
raise api_exc.Forbidden(
527549
detail="Activation is disabled and cannot be run."
@@ -616,6 +638,32 @@ def _check_deleting(self, activation):
616638
detail="Object is being deleted", code=409
617639
)
618640

641+
def _check_workers_offline_with_force(
642+
self, activation, force_flag, operation_name
643+
):
644+
"""
645+
Check if activation is in WORKERS_OFFLINE status and handle force flag logic.
646+
647+
Args:
648+
activation: The activation object to check
649+
force_flag: Boolean indicating if force operation is requested
650+
operation_name: String name of the operation (e.g., "Restarted", "Disabled", "Deleted")
651+
652+
Raises:
653+
api_exc.Conflict: If activation is WORKERS_OFFLINE and force flag is False
654+
"""
655+
if (
656+
activation.status == ActivationStatus.WORKERS_OFFLINE
657+
and not force_flag
658+
):
659+
raise api_exc.Conflict(
660+
f"An activation with an activation_status of 'Workers offline' "
661+
f"cannot be {operation_name} because this may leave an orphaned "
662+
f"container running. "
663+
f"If you want to force a {operation_name.lower()}, please add the "
664+
f"/?force=true query param."
665+
)
666+
619667

620668
@extend_schema_view(
621669
retrieve=extend_schema(

tests/integration/api/test_activation.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,70 @@ def test_disable_activation_redis_unavailable(
836836
}
837837

838838

839+
@pytest.mark.django_db
840+
@pytest.mark.parametrize(
841+
("force_disable", "expected_response"),
842+
[
843+
(
844+
"true",
845+
status.HTTP_204_NO_CONTENT,
846+
),
847+
(
848+
"false",
849+
status.HTTP_409_CONFLICT,
850+
),
851+
],
852+
)
853+
@patch("aap_eda.api.serializers.activation.settings.DEPLOYMENT_TYPE", "podman")
854+
def test_disable_activation_workers_offline(
855+
force_disable,
856+
expected_response,
857+
default_activation: models.Activation,
858+
admin_client: APIClient,
859+
preseed_credential_types,
860+
):
861+
default_activation.status = enums.ActivationStatus.WORKERS_OFFLINE
862+
default_activation.save(update_fields=["status"])
863+
864+
response = admin_client.post(
865+
f"{api_url_v1}/activations/{default_activation.id}/disable/"
866+
f"?force={force_disable}"
867+
)
868+
assert response.status_code == expected_response
869+
870+
871+
@pytest.mark.django_db
872+
@pytest.mark.parametrize(
873+
("force_delete", "expected_response"),
874+
[
875+
(
876+
"true",
877+
status.HTTP_204_NO_CONTENT,
878+
),
879+
(
880+
"false",
881+
status.HTTP_409_CONFLICT,
882+
),
883+
],
884+
)
885+
@patch("aap_eda.api.serializers.activation.settings.DEPLOYMENT_TYPE", "podman")
886+
def test_delete_activation_workers_offline(
887+
force_delete,
888+
expected_response,
889+
default_activation: models.Activation,
890+
admin_client: APIClient,
891+
preseed_credential_types,
892+
):
893+
default_activation.status = enums.ActivationStatus.WORKERS_OFFLINE
894+
default_activation.save(update_fields=["status"])
895+
896+
response = admin_client.delete(
897+
f"{api_url_v1}/activations/{default_activation.id}/"
898+
f"?force={force_delete}"
899+
)
900+
assert response.status_code == expected_response
901+
902+
839903
@pytest.mark.django_db
840904
def test_list_activation_instances(
841905
default_activation: models.Activation,

0 commit comments

Comments
 (0)