Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ cryptography = ">=42,<43"
kubernetes = "26.1.*"
podman = "5.4.*"
rq-scheduler = "^0.10"
django-ansible-base = { git = "https://github.com/ansible/django-ansible-base.git", tag = "2025.5.8", extras = [
django-ansible-base = { git = "https://github.com/zkayyali812/django-ansible-base.git", branch = "phase2/feature-flags/poc", extras = [
"channel-auth",
"rbac",
"redis-client",
Expand Down
9 changes: 8 additions & 1 deletion src/aap_eda/api/resource_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
# 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.

from ansible_base.feature_flags.models import AAPFlag
from ansible_base.resource_registry.registry import (
ParentResource,
ResourceConfig,
ServiceAPIConfig,
SharedResource,
)
from ansible_base.resource_registry.shared_types import (
FeatureFlagType,
OrganizationType,
TeamType,
UserType,
Expand Down Expand Up @@ -52,4 +53,10 @@ class APIConfig(ServiceAPIConfig):
serializer=OrganizationType, is_provider=False
),
),
ResourceConfig(
AAPFlag,
shared_resource=SharedResource(
serializer=FeatureFlagType, is_provider=False
),
),
)
15 changes: 0 additions & 15 deletions src/aap_eda/settings/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,9 @@
DISPATCHERD_FEATURE_FLAG_NAME = "FEATURE_DISPATCHERD_ENABLED"
ANALYTICS_FEATURE_FLAG_NAME = "FEATURE_EDA_ANALYTICS_ENABLED"

FLAGS = {
ANALYTICS_FEATURE_FLAG_NAME: [
{
"condition": "boolean",
"value": False,
},
],
DISPATCHERD_FEATURE_FLAG_NAME: [
{
"condition": "boolean",
"value": False,
},
],
}

INSTALLED_APPS = [
"daphne",
"flags",
# Django apps
"django.contrib.auth",
"django.contrib.contenttypes",
Expand Down
10 changes: 0 additions & 10 deletions src/aap_eda/settings/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
load_dab_settings,
load_envvars,
load_standard_settings_files,
toggle_feature_flags,
)

from .post_load import post_loading
Expand Down Expand Up @@ -52,13 +51,4 @@
post_loading(DYNACONF)
load_dab_settings(DYNACONF)

# toggle feature flags, considering flags coming from
# /etc/ansible-automation-platform/*.yaml
# and envvars like `EDA_FEATURE_FOO_ENABLED=true
DYNACONF.update(
toggle_feature_flags(DYNACONF),
loader_identifier="settings:toggle_feature_flags",
merge=True,
)

export(__name__, DYNACONF) # export back to django.conf.settings
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
import logging

import pytest
from ansible_base.feature_flags.utils import (
create_initial_data as seed_feature_flags,
)
from django.conf import settings

from aap_eda.core import enums, models
Expand Down Expand Up @@ -91,6 +94,11 @@ def aap_credential_type(preseed_credential_types):
)


@pytest.fixture
def preseed_feature_flags():
seed_feature_flags()


#################################################################
# Redis
#################################################################
Expand Down
38 changes: 14 additions & 24 deletions tests/integration/api/test_feature_flags.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
import pytest
from ansible_base.lib.dynamic_config import toggle_feature_flags
from ansible_base.feature_flags.models import AAPFlag
from ansible_base.feature_flags.utils import (
create_initial_data as seed_feature_flags,
)
from django.conf import settings
from django.test import override_settings
from flags.state import flag_state
from flags.state import flag_state, get_flags
from rest_framework import status

from tests.integration.constants import api_url_v1


@pytest.mark.django_db
def test_feature_flags_list_endpoint(admin_client):
def test_feature_flags_list_endpoint(admin_client, preseed_feature_flags):
response = admin_client.get(f"{api_url_v1}/feature_flags_state/")
assert response.status_code == status.HTTP_200_OK, response.data
# Validates expected default feature flags
# Modify each time a flag is added to default settings
assert len(response.data) == 2
assert len(response.data) == len(get_flags())
assert response.data[settings.ANALYTICS_FEATURE_FLAG_NAME] is False
assert response.data[settings.DISPATCHERD_FEATURE_FLAG_NAME] is False


@override_settings(
FLAGS={
"FEATURE_SOME_PLATFORM_FLAG_ENABLED": [
{"condition": "boolean", "value": False},
],
},
FEATURE_SOME_PLATFORM_FLAG_ENABLED=True,
)
@pytest.mark.parametrize("flag_value", [True, False])
@pytest.mark.django_db
def test_feature_flags_toggle():
settings_override = {
"FLAGS": settings.FLAGS,
"FEATURE_SOME_PLATFORM_FLAG_ENABLED": settings.FEATURE_SOME_PLATFORM_FLAG_ENABLED, # noqa: E501
}
assert toggle_feature_flags(settings_override) == {
"FLAGS__FEATURE_SOME_PLATFORM_FLAG_ENABLED": [
{"condition": "boolean", "value": True},
]
}
assert flag_state("FEATURE_SOME_PLATFORM_FLAG_ENABLED") is True
def test_feature_flags_toggle(flag_value):
flag_name = "FEATURE_EDA_ANALYTICS_ENABLED"
setattr(settings, flag_name, flag_value)
AAPFlag.objects.all().delete()
seed_feature_flags()
assert flag_state(flag_name) is flag_value
3 changes: 3 additions & 0 deletions tests/integration/api/test_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@
"/organizations/",
"/teams/",
"/event-streams/",
"feature_flags/states/",
# To be removed after all components
# have migrated away from this endpoint
"/feature_flags_state/",
],
False,
Expand Down
46 changes: 22 additions & 24 deletions tests/unit/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"""Unit tests for feature flags functionality."""

import pytest
from ansible_base.feature_flags.models import AAPFlag
from ansible_base.feature_flags.utils import (
create_initial_data as seed_feature_flags,
)

from aap_eda.settings import features
from aap_eda.settings.features import _get_feature
Expand All @@ -29,14 +33,10 @@ def clear_feature_cache():
@pytest.mark.django_db
def test_get_feature_flag(settings):
"""Test getting feature flag values."""
settings.FLAGS = {
settings.DISPATCHERD_FEATURE_FLAG_NAME: [
{"condition": "boolean", "value": True}
],
settings.ANALYTICS_FEATURE_FLAG_NAME: [
{"condition": "boolean", "value": False}
],
}
AAPFlag.objects.all().delete()
setattr(settings, settings.DISPATCHERD_FEATURE_FLAG_NAME, True)
setattr(settings, settings.ANALYTICS_FEATURE_FLAG_NAME, False)
seed_feature_flags()

assert features.DISPATCHERD is True
assert features.ANALYTICS is False
Expand All @@ -45,40 +45,38 @@ def test_get_feature_flag(settings):
@pytest.mark.django_db
def test_feature_flag_caching(settings):
"""Test that feature flag values are properly cached."""
settings.FLAGS = {
settings.DISPATCHERD_FEATURE_FLAG_NAME: [
{"condition": "boolean", "value": True}
]
}

AAPFlag.objects.all().delete()
setattr(settings, settings.DISPATCHERD_FEATURE_FLAG_NAME, True)
seed_feature_flags()
# First access - should cache the value
assert features.DISPATCHERD is True

# Change the underlying flag value
settings.FLAGS[settings.DISPATCHERD_FEATURE_FLAG_NAME][0]["value"] = False

setattr(settings, settings.DISPATCHERD_FEATURE_FLAG_NAME, False)
seed_feature_flags()
# Should still get the cached value
assert features.DISPATCHERD is True


@pytest.mark.django_db
def test_cache_invalidation(settings):
"""Test that cache invalidation works as expected."""
settings.FLAGS = {
settings.DISPATCHERD_FEATURE_FLAG_NAME: [
{"condition": "boolean", "value": True}
]
}
AAPFlag.objects.all().delete()
setattr(settings, settings.DISPATCHERD_FEATURE_FLAG_NAME, True)
seed_feature_flags()

# Populate cache
assert features.DISPATCHERD is True

# Change the flag value and clear cache
settings.FLAGS[settings.DISPATCHERD_FEATURE_FLAG_NAME][0]["value"] = False
setattr(settings, settings.DISPATCHERD_FEATURE_FLAG_NAME, False)
seed_feature_flags()
_get_feature.cache_clear()

# Should get the new value after cache clear
assert features.DISPATCHERD is False
# Feature should remain true.
# If runtime toggle, we should only be able to
# update the value after toggling it via the platform gateway
assert features.DISPATCHERD is True


@pytest.mark.django_db
Expand Down