diff --git a/src/aap_eda/core/models/activation.py b/src/aap_eda/core/models/activation.py index 35a3d1d20..2c7224af8 100644 --- a/src/aap_eda/core/models/activation.py +++ b/src/aap_eda/core/models/activation.py @@ -15,6 +15,7 @@ from django.db import models from aap_eda.core.enums import ActivationStatus, RestartPolicy +from aap_eda.services.activation.engine.common import ContainerableMixin from .mixins import StatusHandlerModelMixin from .user import AwxToken, User @@ -22,7 +23,7 @@ __all__ = ("Activation",) -class Activation(StatusHandlerModelMixin, models.Model): +class Activation(StatusHandlerModelMixin, ContainerableMixin, models.Model): class Meta: db_table = "core_activation" indexes = [models.Index(fields=["name"], name="ix_activation_name")] diff --git a/src/aap_eda/core/models/event_stream.py b/src/aap_eda/core/models/event_stream.py index b0f058af0..84070a6cf 100644 --- a/src/aap_eda/core/models/event_stream.py +++ b/src/aap_eda/core/models/event_stream.py @@ -12,14 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +import typing as tp + from django.db import models from aap_eda.core.enums import ActivationStatus, RestartPolicy +from aap_eda.services.activation.engine.common import ContainerableMixin from .mixins import StatusHandlerModelMixin -class EventStream(StatusHandlerModelMixin, models.Model): +class EventStream(StatusHandlerModelMixin, ContainerableMixin, models.Model): """Model representing an event stream.""" name = models.TextField(null=False, unique=True) @@ -97,3 +100,10 @@ class Meta: def __str__(self) -> str: return f"EventStream {self.name} ({self.id})" + + # Implementation of the ContainerableMixin. + def get_command_line_parameters(self) -> dict[str, tp.Any]: + params = super().get_command_line_parameters() + return params | { + "skip_audit_events": True, + } diff --git a/src/aap_eda/services/activation/engine/common.py b/src/aap_eda/services/activation/engine/common.py index e7e799bcf..158777a3f 100644 --- a/src/aap_eda/services/activation/engine/common.py +++ b/src/aap_eda/services/activation/engine/common.py @@ -14,14 +14,19 @@ import typing as tp +import uuid from abc import ABC, abstractmethod from datetime import datetime +import yaml from django.conf import settings from pydantic import BaseModel, validator import aap_eda.services.activation.engine.exceptions as exceptions from aap_eda.core.enums import ActivationStatus +from aap_eda.services.auth import create_jwt_token + +from .ports import find_ports class LogHandler(ABC): @@ -56,6 +61,7 @@ class AnsibleRulebookCmdLine(BaseModel): heartbeat: int id: tp.Union[str, int] # casted to str log_level: tp.Optional[str] = None # -v or -vv or None + skip_audit_events: bool = False @validator("id", pre=True) def cast_to_str(cls, v): @@ -82,6 +88,8 @@ def get_args(self, sanitized=False) -> list[str]: "--heartbeat", str(self.heartbeat), ] + if self.skip_audit_events: + args.append("--skip-audit-events") if self.log_level: args.append(self.log_level) @@ -102,8 +110,8 @@ class ContainerRequest(BaseModel): name: str # f"eda-{activation_instance.id}-{uuid.uuid4()}" image_url: str # quay.io/ansible/ansible-rulebook:main cmdline: AnsibleRulebookCmdLine - activation_instance_id: str - activation_id: str + rulebook_process_id: int + process_parent_id: int credential: tp.Optional[Credential] = None ports: tp.Optional[list[tuple]] = None pull_policy: str = settings.DEFAULT_PULL_POLICY # Always by default @@ -113,6 +121,162 @@ class ContainerRequest(BaseModel): extra_args: tp.Optional[dict] = None +class ContainerableMixinError(Exception): + """Base class for exceptions from implementers of ContainerableMixin.""" + + pass + + +class ContainerableInvalidError(ContainerableMixinError): + pass + + +class ContainerableNoLatestInstanceError(ContainerableMixinError): + pass + + +# To use ContainerableMixin the model class adding the mixin is required to +# have the following attributes (or property getters): +# +# Attribute Type +# --------- ---- +# decision_environment DecisionEnvironment +# extra_var ExtraVar +# latest_instance RulebookProcess +# restart_policy str +# rulebook_rulesets str +# +class ContainerableMixin: + def get_command_line_parameters(self) -> dict[str, tp.Any]: + """Return parameters for running ansible-rulebook.""" + self.validate() + + access_token, refresh_token = create_jwt_token() + return { + "id": str(self.latest_instance.id), + "ws_url": self._get_ws_url(), + "log_level": settings.ANSIBLE_RULEBOOK_LOG_LEVEL, + "ws_ssl_verify": settings.WEBSOCKET_SSL_VERIFY, + "ws_token_url": self._get_ws_token_url(), + "ws_access_token": access_token, + "ws_refresh_token": refresh_token, + "heartbeat": settings.RULEBOOK_LIVENESS_CHECK_SECONDS, + "skip_audit_events": False, + } + + def get_container_parameters(self) -> dict[str, tp.Any]: + """Return parameters used to create a ContainerRquest.""" + self.validate() + + return { + "credential": self._get_image_credential(), + "name": self._get_container_name(), + "image_url": self.decision_environment.image_url, + "ports": self._get_ports(), + "env_vars": settings.PODMAN_ENV_VARS, + "extra_args": settings.PODMAN_EXTRA_ARGS, + "mem_limit": settings.PODMAN_MEM_LIMIT, + "mounts": settings.PODMAN_MOUNTS, + "process_parent_id": self.id, + "rulebook_process_id": self.latest_instance.id, + "cmdline": self._build_cmdline(), + } + + def get_container_request(self) -> ContainerRequest: + """Return ContainerRequest used for creation.""" + params = self.get_container_parameters() + return ContainerRequest( + credential=params["credential"], + name=params["name"], + image_url=params["image_url"], + ports=params["ports"], + process_parent_id=params["process_parent_id"], + rulebook_process_id=params["rulebook_process_id"], + env_vars=params["env_vars"], + extra_args=params["extra_args"], + mem_limit=params["mem_limit"], + mounts=params["mounts"], + cmdline=params["cmdline"], + ) + + def get_restart_policy(self) -> str: + """Return the restart policy for the implementer. + + We don't validate here as validation is for use to create a new + container and the value of the restart policy is not a determinate of + that. + """ + return self.restart_policy + + def validate(self): + """Validate the the implementer is appropriate to be containerized.""" + try: + self._validate() + except ContainerableMixinError as e: + raise ContainerableInvalidError from e + + def _build_cmdline(self) -> AnsibleRulebookCmdLine: + params = self.get_command_line_parameters() + return AnsibleRulebookCmdLine( + ws_url=params["ws_url"], + log_level=params["log_level"], + ws_ssl_verify=params["ws_ssl_verify"], + ws_access_token=params["ws_access_token"], + ws_refresh_token=params["ws_refresh_token"], + ws_token_url=params["ws_token_url"], + heartbeat=params["heartbeat"], + id=params["id"], + skip_audit_events=params["skip_audit_events"], + ) + + def _get_container_name(self) -> str: + """Return the name to use for the ContainerRequest.""" + return ( + f"{settings.CONTAINER_NAME_PREFIX}-{self.latest_instance.id}" + f"-{uuid.uuid4()}" + ) + + def _get_context(self) -> dict[str, tp.Any]: + """Return the context dictionary used to create a ContainerRquest.""" + if self.extra_var: + context = yaml.safe_load(self.extra_var.extra_var) + else: + context = {} + return context + + def _get_image_credential(self) -> tp.Optional[Credential]: + """Return a decrypted Credential or None for the implementer.""" + credential = self.decision_environment.credential + if credential: + return Credential( + username=credential.username, + secret=credential.secret.get_secret_value(), + ) + return None + + def _get_ports(self) -> list[tuple]: + return find_ports(self.rulebook_rulesets, self._get_context()) + + def _get_ws_url(self) -> str: + return f"{settings.WEBSOCKET_BASE_URL}{self._get_ws_url_subpath()}" + + def _get_ws_url_subpath(self) -> str: + return f"/{settings.API_PREFIX}/ws/ansible-rulebook" + + def _get_ws_token_url(self) -> str: + return ( + f"{settings.WEBSOCKET_TOKEN_BASE_URL}" + f"{self._get_ws_token_url_subpath()}" + ) + + def _get_ws_token_url_subpath(self) -> str: + return f"/{settings.API_PREFIX}/v1/auth/token/refresh/" + + def _validate(self): + if not self.latest_instance: + raise ContainerableNoLatestInstanceError + + class ContainerStatus(BaseModel): status: ActivationStatus message: str = "" diff --git a/src/aap_eda/services/activation/engine/kubernetes.py b/src/aap_eda/services/activation/engine/kubernetes.py index e3a0deef4..c9bd6b051 100644 --- a/src/aap_eda/services/activation/engine/kubernetes.py +++ b/src/aap_eda/services/activation/engine/kubernetes.py @@ -101,12 +101,12 @@ def start(self, request: ContainerRequest, log_handler: LogHandler) -> str: # TODO : Should this be compatible with the previous version # Previous Version self.job_name = ( - f"{self.resource_prefix}-job-{request.activation_id}" - f"-{request.activation_instance_id}" + f"{self.resource_prefix}-job-{request.process_parent_id}" + f"-{request.rulebook_process_id}" ) self.pod_name = ( - f"{self.resource_prefix}-pod-{request.activation_id}" - f"-{request.activation_instance_id}" + f"{self.resource_prefix}-pod-{request.process_parent_id}" + f"-{request.rulebook_process_id}" ) # Should we switch to new format diff --git a/src/aap_eda/services/activation/manager.py b/src/aap_eda/services/activation/manager.py index cb74589ec..28453fd11 100644 --- a/src/aap_eda/services/activation/manager.py +++ b/src/aap_eda/services/activation/manager.py @@ -16,11 +16,9 @@ import contextlib import logging import typing as tp -import uuid from datetime import timedelta from functools import wraps -import yaml from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db import transaction @@ -37,24 +35,16 @@ ) from aap_eda.services.activation import exceptions from aap_eda.services.activation.engine import exceptions as engine_exceptions -from aap_eda.services.activation.engine.common import ( - AnsibleRulebookCmdLine, - ContainerRequest, - Credential, -) +from aap_eda.services.activation.engine.common import ContainerRequest from aap_eda.services.activation.restart_helper import ( system_restart_activation, ) -from aap_eda.services.auth import create_jwt_token from .db_log_handler import DBLogger -from .engine.common import ContainerEngine +from .engine.common import ContainerableInvalidError, ContainerEngine from .engine.factory import new_container_engine -from .engine.ports import find_ports LOGGER = logging.getLogger(__name__) -ACTIVATION_PATH = f"/{settings.API_PREFIX}/ws/ansible-rulebook" -TOKEN_RENEW_PATH = f"/{settings.API_PREFIX}/v1/auth/token/refresh/" class HasDbInstance(tp.Protocol): @@ -282,7 +272,7 @@ def _start_activation_instance(self): # start the container try: - container_request = self._build_container_request() + container_request = self._get_container_request() container_id = self.container_engine.start( container_request, log_handler, @@ -1060,55 +1050,13 @@ def _create_activation_instance(self): self._error_activation(msg) raise exceptions.ActivationStartError(msg) from exc - def _build_container_request(self) -> ContainerRequest: - if self.db_instance.extra_var: - context = yaml.safe_load(self.db_instance.extra_var.extra_var) - else: - context = {} - - return ContainerRequest( - credential=self._build_credential(), - cmdline=self._build_cmdline(), - name=( - f"{settings.CONTAINER_NAME_PREFIX}-{self.latest_instance.id}" - f"-{uuid.uuid4()}" - ), - image_url=self.db_instance.decision_environment.image_url, - ports=find_ports(self.db_instance.rulebook_rulesets, context), - activation_id=self.db_instance.id, - activation_instance_id=self.latest_instance.id, - env_vars=settings.PODMAN_ENV_VARS, - extra_args=settings.PODMAN_EXTRA_ARGS, - mem_limit=settings.PODMAN_MEM_LIMIT, - mounts=settings.PODMAN_MOUNTS, - ) - - def _build_credential(self) -> tp.Optional[Credential]: - credential = self.db_instance.decision_environment.credential - if credential: - return Credential( - username=credential.username, - secret=credential.secret.get_secret_value(), - ) - return None - - def _build_cmdline(self) -> AnsibleRulebookCmdLine: - if not self.latest_instance: + def _get_container_request(self) -> ContainerRequest: + try: + return self.db_instance.get_container_request() + except ContainerableInvalidError: msg = ( - f"Activation {self.db_instance.id} does not have an instance, " - "cmdline can not be built." + f"Activation {self.db_instance.id} not valid, " + "container request cannot be built." ) LOGGER.exception(msg) raise exceptions.ActivationManagerError(msg) - - access_token, refresh_token = create_jwt_token() - return AnsibleRulebookCmdLine( - ws_url=settings.WEBSOCKET_BASE_URL + ACTIVATION_PATH, - log_level=settings.ANSIBLE_RULEBOOK_LOG_LEVEL, - ws_ssl_verify=settings.WEBSOCKET_SSL_VERIFY, - ws_access_token=access_token, - ws_refresh_token=refresh_token, - ws_token_url=settings.WEBSOCKET_TOKEN_BASE_URL + TOKEN_RENEW_PATH, - heartbeat=settings.RULEBOOK_LIVENESS_CHECK_SECONDS, - id=str(self.latest_instance.id), - ) diff --git a/src/aap_eda/services/auth.py b/src/aap_eda/services/auth.py index b18b3d29f..1332d1d6f 100644 --- a/src/aap_eda/services/auth.py +++ b/src/aap_eda/services/auth.py @@ -16,7 +16,7 @@ from rest_framework_simplejwt.tokens import RefreshToken -from aap_eda.core.models import User +from aap_eda.core.models.user import User def group_permission_resource(permission_data): diff --git a/tests/integration/services/activation/conftest.py b/tests/integration/services/activation/conftest.py new file mode 100644 index 000000000..6ea3921ca --- /dev/null +++ b/tests/integration/services/activation/conftest.py @@ -0,0 +1,63 @@ +# Copyright 2024 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Common Attributes for Activation Manager Tests.""" + +import pytest + +from aap_eda.core import models + + +@pytest.fixture +def default_decision_environment() -> models.DecisionEnvironment: + """Return a default decision environment.""" + return models.DecisionEnvironment.objects.create( + name="test-decision-environment", + image_url="localhost:14000/test-image-url", + ) + + +@pytest.fixture +def default_rulebook() -> models.Rulebook: + """Return a default rulebook.""" + rulesets = """ +--- +- name: Hello World + hosts: all + sources: + - ansible.eda.range: + limit: 5 + rules: + - name: Say Hello + condition: event.i == 1 + action: + debug: + msg: "Hello World!" + +""" + return models.Rulebook.objects.create( + name="test-rulebook", + rulesets=rulesets, + ) + + +@pytest.fixture +def default_user() -> models.User: + """Return a default user.""" + user = models.User.objects.create( + username="test.user", + password="test.user.123", + email="test.user@localhost", + ) + + return user diff --git a/tests/integration/services/activation/engine/test_kubernetes.py b/tests/integration/services/activation/engine/test_kubernetes.py index bc648818b..365dfa416 100644 --- a/tests/integration/services/activation/engine/test_kubernetes.py +++ b/tests/integration/services/activation/engine/test_kubernetes.py @@ -95,8 +95,8 @@ def get_request(data: InitData): return ContainerRequest( name="test-request", image_url="quay.io/ansible/ansible-rulebook:main", - activation_instance_id=data.activation_instance.id, - activation_id=data.activation.id, + rulebook_process_id=data.activation_instance.id, + process_parent_id=data.activation.id, cmdline=get_ansible_rulebook_cmdline(data), credential=Credential(username="admin", secret="secret"), ports=[("localhost", 8080)], diff --git a/tests/integration/services/activation/engine/test_podman.py b/tests/integration/services/activation/engine/test_podman.py index b361ca537..802120101 100644 --- a/tests/integration/services/activation/engine/test_podman.py +++ b/tests/integration/services/activation/engine/test_podman.py @@ -93,8 +93,8 @@ def get_request(data: InitData): return ContainerRequest( name="test-request", image_url="quay.io/ansible/ansible-rulebook:main", - activation_instance_id=data.activation_instance.id, - activation_id=data.activation.id, + rulebook_process_id=data.activation_instance.id, + process_parent_id=data.activation.id, cmdline=get_ansible_rulebook_cmdline(data), ports=[("localhost", 8080)], mem_limit="8G", @@ -108,8 +108,8 @@ def get_request_with_never_pull_policy(data: InitData): return ContainerRequest( name="test-request", image_url="quay.io/ansible/ansible-rulebook:main", - activation_instance_id=data.activation_instance.id, - activation_id=data.activation.id, + rulebook_process_id=data.activation_instance.id, + process_parent_id=data.activation.id, cmdline=get_ansible_rulebook_cmdline(data), pull_policy="Never", ) @@ -119,8 +119,8 @@ def get_request_with_credential(data: InitData): return ContainerRequest( name="test-request", image_url="quay.io/ansible/ansible-rulebook:main", - activation_instance_id=data.activation_instance.id, - activation_id=data.activation.id, + rulebook_process_id=data.activation_instance.id, + process_parent_id=data.activation.id, cmdline=get_ansible_rulebook_cmdline(data), credential=Credential(username="me", secret="secret"), ) diff --git a/tests/integration/services/activation/test_activation.py b/tests/integration/services/activation/test_activation.py new file mode 100644 index 000000000..a9237a737 --- /dev/null +++ b/tests/integration/services/activation/test_activation.py @@ -0,0 +1,103 @@ +# Copyright 2024 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Activation Containerable Interface tests.""" + +import pytest + +from aap_eda.core import models +from aap_eda.services.activation.engine.common import ( + ContainerableInvalidError, + ContainerRequest, +) + +PROJECT_GIT_HASH = "684f62df18ce5f8d5c428e53203b9b975426eed0" + + +@pytest.fixture +def activation_no_instance( + default_user: models.User, + default_decision_environment: models.DecisionEnvironment, + default_rulebook: models.Rulebook, +) -> models.Activation: + """Return an activation with outassociated RulebookProcess.""" + return models.Activation.objects.create( + name="test-activation", + user=default_user, + decision_environment=default_decision_environment, + rulebook=default_rulebook, + # rulebook_rulesets is populated by the serializer + rulebook_rulesets=default_rulebook.rulesets, + ) + + +@pytest.fixture +def activation(activation_no_instance) -> models.Activation: + """Return an activation with associated RulebookProcess.""" + models.RulebookProcess.objects.create( + name="activation-instance-1", + activation=activation_no_instance, + git_hash=PROJECT_GIT_HASH, + ) + return activation_no_instance + + +@pytest.mark.django_db +def test_command_line_parameters(activation): + params = activation.get_command_line_parameters() + assert params["ws_url"] is not None + assert params["log_level"] is not None + assert params["ws_ssl_verify"] is not None + assert params["ws_token_url"] is not None + assert params["ws_access_token"] is not None + assert params["ws_refresh_token"] is not None + assert params["heartbeat"] is not None + assert not params["skip_audit_events"] + assert params["id"] == str(activation.latest_instance.id) + + +@pytest.mark.django_db +def test_container_parameters(activation): + params = activation.get_container_parameters() + assert params["name"] is not None + assert params["image_url"] is not None + assert params["ports"] is not None + assert params["env_vars"] is not None + assert params["extra_args"] is not None + assert params["mem_limit"] is not None + assert params["mounts"] is not None + assert params["process_parent_id"] == activation.id + assert params["rulebook_process_id"] == activation.latest_instance.id + + +@pytest.mark.django_db +def test_container_parameters_no_credential(activation): + """Test container params when no credential exists.""" + params = activation.get_container_parameters() + assert params["credential"] is None + + +@pytest.mark.django_db +def test_get_container_request(activation): + """Test the construction of a ContainerRequest.""" + request = activation.get_container_request() + assert isinstance(request, ContainerRequest) + assert not request.cmdline.skip_audit_events + assert "--skip-audit-events" not in request.cmdline.get_args() + + +@pytest.mark.django_db +def test_get_container_request_no_instance(activation_no_instance): + """Test the construction of a ContainerRequest.""" + with pytest.raises(ContainerableInvalidError): + activation_no_instance.get_container_request() diff --git a/tests/integration/services/activation/test_event_stream.py b/tests/integration/services/activation/test_event_stream.py new file mode 100644 index 000000000..ee88f3a33 --- /dev/null +++ b/tests/integration/services/activation/test_event_stream.py @@ -0,0 +1,103 @@ +# Copyright 2024 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""EventStream Containerable Interface tests.""" + +import pytest + +from aap_eda.core import models +from aap_eda.services.activation.engine.common import ( + ContainerableInvalidError, + ContainerRequest, +) + +PROJECT_GIT_HASH = "684f62df18ce5f8d5c428e53203b9b975426eed0" + + +@pytest.fixture +def event_stream_no_instance( + default_user: models.User, + default_decision_environment: models.DecisionEnvironment, + default_rulebook: models.Rulebook, +) -> models.EventStream: + """Return an event stream without associated RulebookProcess.""" + return models.EventStream.objects.create( + name="test-event-stream", + user=default_user, + decision_environment=default_decision_environment, + rulebook=default_rulebook, + # rulebook_rulesets is populated by the serializer + rulebook_rulesets=default_rulebook.rulesets, + ) + + +@pytest.fixture +def event_stream(event_stream_no_instance) -> models.EventStream: + """Return an activation with associated RulebookProcess.""" + models.RulebookProcess.objects.create( + name="event-stream-instance-1", + event_stream=event_stream_no_instance, + git_hash=PROJECT_GIT_HASH, + ) + return event_stream_no_instance + + +@pytest.mark.django_db +def test_command_line_parameters(event_stream): + params = event_stream.get_command_line_parameters() + assert params["ws_url"] is not None + assert params["log_level"] is not None + assert params["ws_ssl_verify"] is not None + assert params["ws_token_url"] is not None + assert params["ws_access_token"] is not None + assert params["ws_refresh_token"] is not None + assert params["heartbeat"] is not None + assert params["skip_audit_events"] + assert params["id"] == str(event_stream.latest_instance.id) + + +@pytest.mark.django_db +def test_container_parameters(event_stream): + params = event_stream.get_container_parameters() + assert params["name"] is not None + assert params["image_url"] is not None + assert params["ports"] is not None + assert params["env_vars"] is not None + assert params["extra_args"] is not None + assert params["mem_limit"] is not None + assert params["mounts"] is not None + assert params["process_parent_id"] == event_stream.id + assert params["rulebook_process_id"] == event_stream.latest_instance.id + + +@pytest.mark.django_db +def test_container_parameters_no_credential(event_stream): + """Test container params when no credential exists.""" + params = event_stream.get_container_parameters() + assert params["credential"] is None + + +@pytest.mark.django_db +def test_get_container_request(event_stream): + """Test the construction of a ContainerRequest.""" + request = event_stream.get_container_request() + assert isinstance(request, ContainerRequest) + assert request.cmdline.skip_audit_events + assert "--skip-audit-events" in request.cmdline.get_args() + + +@pytest.mark.django_db +def test_get_container_request_no_instance(event_stream_no_instance): + """Test the construction of a ContainerRequest.""" + with pytest.raises(ContainerableInvalidError): + event_stream_no_instance.get_container_request() diff --git a/tests/integration/services/activation/test_manager.py b/tests/integration/services/activation/test_manager.py index 90de99c00..e185a5e0a 100644 --- a/tests/integration/services/activation/test_manager.py +++ b/tests/integration/services/activation/test_manager.py @@ -24,12 +24,13 @@ from aap_eda.core import models from aap_eda.core.enums import ActivationStatus from aap_eda.services.activation.engine import exceptions as engine_exceptions -from aap_eda.services.activation.engine.common import ContainerEngine +from aap_eda.services.activation.engine.common import ( + ContainerEngine, + ContainerRequest, +) from aap_eda.services.activation.manager import ( - ACTIVATION_PATH, LOGGER, ActivationManager, - AnsibleRulebookCmdLine, exceptions, ) @@ -40,30 +41,6 @@ def apply_settings(settings: SettingsWrapper, **kwargs): setattr(settings, key, value) -@pytest.fixture -def default_rulebook() -> models.Rulebook: - """Return a default rulebook.""" - rulesets = """ ---- -- name: Hello World - hosts: all - sources: - - ansible.eda.range: - limit: 5 - rules: - - name: Say Hello - condition: event.i == 1 - action: - debug: - msg: "Hello World!" - -""" - return models.Rulebook.objects.create( - name="test-rulebook", - rulesets=rulesets, - ) - - @pytest.fixture def rulebook_with_job_template() -> models.Rulebook: """Return a default rulebook.""" @@ -94,27 +71,6 @@ def eda_caplog(caplog_factory) -> LogCaptureFixture: return caplog_factory(LOGGER) -@pytest.fixture -def default_user() -> models.User: - """Return a default user.""" - user = models.User.objects.create( - username="test.user", - password="test.user.123", - email="test.user@localhost", - ) - - return user - - -@pytest.fixture -def decision_environment() -> models.DecisionEnvironment: - """Return a default decision environment.""" - return models.DecisionEnvironment.objects.create( - name="test-decision-environment", - image_url="localhost:14000/test-image-url", - ) - - @pytest.fixture def activation_with_instance( basic_activation: models.Activation, @@ -141,14 +97,14 @@ def running_activation(activation_with_instance: models.Activation): @pytest.fixture def basic_activation( default_user: models.User, - decision_environment: models.DecisionEnvironment, + default_decision_environment: models.DecisionEnvironment, default_rulebook: models.Rulebook, ) -> models.Activation: """Return the minimal activation.""" return models.Activation.objects.create( name="test-activation", user=default_user, - decision_environment=decision_environment, + decision_environment=default_decision_environment, rulebook=default_rulebook, # rulebook_rulesets is populated by the serializer rulebook_rulesets=default_rulebook.rulesets, @@ -161,7 +117,7 @@ def container_engine_mock() -> MagicMock: @pytest.mark.django_db -def test_build_cmdline( +def test_get_container_request( activation_with_instance: models.Activation, settings: SettingsWrapper, ): @@ -174,12 +130,10 @@ def test_build_cmdline( } apply_settings(settings, **override_settings) activation_manager = ActivationManager(activation_with_instance) - cmdline = activation_manager._build_cmdline() - assert isinstance(cmdline, AnsibleRulebookCmdLine) - assert ( - cmdline.ws_url - == override_settings["WEBSOCKET_BASE_URL"] + ACTIVATION_PATH - ) + request = activation_manager._get_container_request() + assert isinstance(request, ContainerRequest) + cmdline = request.cmdline + assert cmdline.ws_url.startswith(override_settings["WEBSOCKET_BASE_URL"]) assert cmdline.log_level == override_settings["ANSIBLE_RULEBOOK_LOG_LEVEL"] assert cmdline.ws_ssl_verify == override_settings["WEBSOCKET_SSL_VERIFY"] assert ( @@ -190,18 +144,11 @@ def test_build_cmdline( @pytest.mark.django_db -def test_build_cmdline_no_instance(basic_activation): +def test_get_container_request_no_instance(basic_activation): """Test build_cmdline when no instance exists.""" activation_manager = ActivationManager(basic_activation) with pytest.raises(exceptions.ActivationManagerError): - activation_manager._build_cmdline() - - -@pytest.mark.django_db -def test_build_credential_inexistent(basic_activation): - """Test build_credential when no credential exists.""" - activation_manager = ActivationManager(basic_activation) - assert activation_manager._build_credential() is None + activation_manager._get_container_request() @pytest.mark.django_db