mirror of
https://github.com/ansible/awx.git
synced 2026-01-10 15:32:07 -03:30
* clear LICENSE from cache on change * Adds tests for license cache clearing Generated by Cursor (claude-4-sonnet) * test fixes Generated with Cursor (claude-4-sonnet) --------- Signed-off-by: Robin Y Bobbitt <rbobbitt@redhat.com> Co-authored-by: Jake Jackson <jljacks93@gmail.com>
This commit is contained in:
parent
9033e829fe
commit
11f31ef796
@ -9,6 +9,7 @@ from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.db import connection
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
@ -27,6 +28,7 @@ from awx.api.generics import APIView
|
||||
from awx.conf.registry import settings_registry
|
||||
from awx.main.analytics import all_collectors
|
||||
from awx.main.ha import is_ha_environment
|
||||
from awx.main.tasks.system import clear_setting_cache
|
||||
from awx.main.utils import get_awx_version, get_custom_venv_choices
|
||||
from awx.main.utils.licensing import validate_entitlement_manifest
|
||||
from awx.api.versioning import URLPathVersioning, is_optional_api_urlpattern_prefix_request, reverse, drf_reverse
|
||||
@ -268,6 +270,7 @@ class ApiV2AttachView(APIView):
|
||||
if sub['subscription_id'] == subscription_id:
|
||||
sub['valid_key'] = True
|
||||
settings.LICENSE = sub
|
||||
connection.on_commit(lambda: clear_setting_cache.delay(['LICENSE']))
|
||||
return Response(sub)
|
||||
|
||||
return Response({"error": _("Error processing subscription metadata.")}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -287,7 +290,6 @@ class ApiV2ConfigView(APIView):
|
||||
'''Return various sitewide configuration settings'''
|
||||
|
||||
license_data = get_licenser().validate()
|
||||
|
||||
if not license_data.get('valid_key', False):
|
||||
license_data = {}
|
||||
|
||||
@ -364,6 +366,7 @@ class ApiV2ConfigView(APIView):
|
||||
|
||||
try:
|
||||
license_data_validated = get_licenser().license_from_manifest(license_data)
|
||||
connection.on_commit(lambda: clear_setting_cache.delay(['LICENSE']))
|
||||
except Exception:
|
||||
logger.warning(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -382,6 +385,7 @@ class ApiV2ConfigView(APIView):
|
||||
def delete(self, request):
|
||||
try:
|
||||
settings.LICENSE = {}
|
||||
connection.on_commit(lambda: clear_setting_cache.delay(['LICENSE']))
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
except Exception:
|
||||
# FIX: Log
|
||||
|
||||
191
awx/main/tests/functional/api/test_license_cache_clearing.py
Normal file
191
awx/main/tests/functional/api/test_license_cache_clearing.py
Normal file
@ -0,0 +1,191 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from awx.api.versioning import reverse
|
||||
|
||||
|
||||
# Generated by Cursor (claude-4-sonnet)
|
||||
@pytest.mark.django_db
|
||||
class TestLicenseCacheClearing:
|
||||
"""Test cache clearing for LICENSE setting changes"""
|
||||
|
||||
def test_license_from_manifest_clears_cache(self, admin_user, post):
|
||||
"""Test that posting a manifest to /api/v2/config/ clears the LICENSE cache"""
|
||||
|
||||
# Mock the licenser and clear_setting_cache
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
|
||||
'awx.api.views.root.clear_setting_cache'
|
||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
# Set up mock license data
|
||||
mock_license_data = {'valid_key': True, 'license_type': 'enterprise', 'instance_count': 100, 'subscription_name': 'Test Enterprise License'}
|
||||
|
||||
# Mock the validation and license processing
|
||||
mock_validate.return_value = [{'some': 'manifest_data'}]
|
||||
mock_licenser = MagicMock()
|
||||
mock_licenser.license_from_manifest.return_value = mock_license_data
|
||||
mock_get_licenser.return_value = mock_licenser
|
||||
|
||||
# Prepare the request data (base64 encoded manifest)
|
||||
manifest_data = {'manifest': 'ZmFrZS1tYW5pZmVzdC1kYXRh'} # base64 for "fake-manifest-data"
|
||||
|
||||
# Make the POST request
|
||||
url = reverse('api:api_v2_config_view')
|
||||
response = post(url, manifest_data, admin_user, expect=200)
|
||||
|
||||
# Verify the response
|
||||
assert response.data == mock_license_data
|
||||
|
||||
# Verify license_from_manifest was called
|
||||
mock_licenser.license_from_manifest.assert_called_once()
|
||||
|
||||
# Verify on_commit was called (may be multiple times due to other settings)
|
||||
assert mock_on_commit.call_count >= 1
|
||||
|
||||
# Execute all on_commit callbacks to trigger cache clearing
|
||||
for call_args in mock_on_commit.call_args_list:
|
||||
callback = call_args[0][0]
|
||||
callback()
|
||||
|
||||
# Verify that clear_setting_cache.delay was called with ['LICENSE']
|
||||
mock_clear_cache.delay.assert_any_call(['LICENSE'])
|
||||
|
||||
def test_config_delete_clears_cache(self, admin_user, delete):
|
||||
"""Test that DELETE /api/v2/config/ clears the LICENSE cache"""
|
||||
|
||||
with patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
# Make the DELETE request
|
||||
url = reverse('api:api_v2_config_view')
|
||||
delete(url, admin_user, expect=204)
|
||||
|
||||
# Verify on_commit was called at least once
|
||||
assert mock_on_commit.call_count >= 1
|
||||
|
||||
# Execute all on_commit callbacks to trigger cache clearing
|
||||
for call_args in mock_on_commit.call_args_list:
|
||||
callback = call_args[0][0]
|
||||
callback()
|
||||
|
||||
mock_clear_cache.delay.assert_called_once_with(['LICENSE'])
|
||||
|
||||
def test_attach_view_clears_cache(self, admin_user, post):
|
||||
"""Test that posting to /api/v2/config/attach/ clears the LICENSE cache"""
|
||||
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch(
|
||||
'django.db.connection.on_commit'
|
||||
) as mock_on_commit, patch('awx.api.views.root.settings') as mock_settings:
|
||||
|
||||
# Set up subscription credentials in settings
|
||||
mock_settings.SUBSCRIPTIONS_CLIENT_ID = 'test-client-id'
|
||||
mock_settings.SUBSCRIPTIONS_CLIENT_SECRET = 'test-client-secret'
|
||||
|
||||
# Set up mock licenser with validated subscriptions
|
||||
mock_licenser = MagicMock()
|
||||
subscription_data = {'subscription_id': 'test-subscription-123', 'valid_key': False, 'license_type': 'enterprise', 'instance_count': 50}
|
||||
mock_licenser.validate_rh.return_value = [subscription_data]
|
||||
mock_get_licenser.return_value = mock_licenser
|
||||
|
||||
# Prepare request data
|
||||
request_data = {'subscription_id': 'test-subscription-123'}
|
||||
|
||||
# Make the POST request
|
||||
url = reverse('api:api_v2_attach_view')
|
||||
response = post(url, request_data, admin_user, expect=200)
|
||||
|
||||
# Verify the response includes valid_key=True
|
||||
assert response.data['valid_key'] is True
|
||||
assert response.data['subscription_id'] == 'test-subscription-123'
|
||||
|
||||
# Verify settings.LICENSE was set
|
||||
expected_license = subscription_data.copy()
|
||||
expected_license['valid_key'] = True
|
||||
assert mock_settings.LICENSE == expected_license
|
||||
|
||||
# Verify cache clearing was scheduled
|
||||
mock_on_commit.assert_called_once()
|
||||
call_args = mock_on_commit.call_args[0][0] # Get the lambda function
|
||||
|
||||
# Execute the lambda to verify it calls clear_setting_cache
|
||||
call_args()
|
||||
mock_clear_cache.delay.assert_called_once_with(['LICENSE'])
|
||||
|
||||
def test_attach_view_subscription_not_found_no_cache_clear(self, admin_user, post):
|
||||
"""Test that attach view doesn't clear cache when subscription is not found"""
|
||||
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch(
|
||||
'django.db.connection.on_commit'
|
||||
) as mock_on_commit:
|
||||
|
||||
# Set up mock licenser with different subscription
|
||||
mock_licenser = MagicMock()
|
||||
subscription_data = {'subscription_id': 'different-subscription-456', 'valid_key': False, 'license_type': 'enterprise'} # Different ID
|
||||
mock_licenser.validate_rh.return_value = [subscription_data]
|
||||
mock_get_licenser.return_value = mock_licenser
|
||||
|
||||
# Request data with non-matching subscription ID
|
||||
request_data = {
|
||||
'subscription_id': 'test-subscription-123', # This won't match
|
||||
}
|
||||
|
||||
# Make the POST request
|
||||
url = reverse('api:api_v2_attach_view')
|
||||
response = post(url, request_data, admin_user, expect=400)
|
||||
|
||||
# Verify error response
|
||||
assert 'error' in response.data
|
||||
|
||||
# Verify cache clearing was NOT called (no matching subscription)
|
||||
mock_on_commit.assert_not_called()
|
||||
mock_clear_cache.delay.assert_not_called()
|
||||
|
||||
def test_manifest_validation_error_no_cache_clear(self, admin_user, post):
|
||||
"""Test that config view doesn't clear cache when manifest validation fails"""
|
||||
|
||||
with patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
|
||||
'awx.api.views.root.clear_setting_cache'
|
||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
# Mock validation to raise ValueError
|
||||
mock_validate.side_effect = ValueError("Invalid manifest")
|
||||
|
||||
# Prepare request data
|
||||
manifest_data = {'manifest': 'aW52YWxpZC1tYW5pZmVzdA=='} # base64 for "invalid-manifest"
|
||||
|
||||
# Make the POST request
|
||||
url = reverse('api:api_v2_config_view')
|
||||
response = post(url, manifest_data, admin_user, expect=400)
|
||||
|
||||
# Verify error response
|
||||
assert response.data['error'] == 'Invalid manifest'
|
||||
|
||||
# Verify cache clearing was NOT called (validation failed)
|
||||
mock_on_commit.assert_not_called()
|
||||
mock_clear_cache.delay.assert_not_called()
|
||||
|
||||
def test_license_processing_error_no_cache_clear(self, admin_user, post):
|
||||
"""Test that config view doesn't clear cache when license processing fails"""
|
||||
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
|
||||
'awx.api.views.root.clear_setting_cache'
|
||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
# Mock validation to succeed but license processing to fail
|
||||
mock_validate.return_value = [{'some': 'manifest_data'}]
|
||||
mock_licenser = MagicMock()
|
||||
mock_licenser.license_from_manifest.side_effect = Exception("License processing failed")
|
||||
mock_get_licenser.return_value = mock_licenser
|
||||
|
||||
# Prepare request data
|
||||
manifest_data = {'manifest': 'ZmFrZS1tYW5pZmVzdA=='} # base64 for "fake-manifest"
|
||||
|
||||
# Make the POST request
|
||||
url = reverse('api:api_v2_config_view')
|
||||
response = post(url, manifest_data, admin_user, expect=400)
|
||||
|
||||
# Verify error response
|
||||
assert response.data['error'] == 'Invalid License'
|
||||
|
||||
# Verify cache clearing was NOT called (license processing failed)
|
||||
mock_on_commit.assert_not_called()
|
||||
mock_clear_cache.delay.assert_not_called()
|
||||
Loading…
x
Reference in New Issue
Block a user