Skip to content

Commit

Permalink
feat: support a custom-subs-catalog allow list
Browse files Browse the repository at this point in the history
Adds a configurable allow list of enterprise catalog uuids that are excused from
violations in the ``validate_num_catalog_queries`` management command.
Also, fix logic in this command to calculate the distinct number
of plan types, instead of only products, for comparison against
the number of used catalog queries founds amongst non-internal-use-only plans.
  • Loading branch information
iloveagent57 committed May 6, 2024
1 parent 092d17c commit 490d99a
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
InvalidCatalogQueryMappingError,
)
from license_manager.apps.subscriptions.tests.factories import (
PlanTypeFactory,
ProductFactory,
SubscriptionPlanFactory,
)
Expand All @@ -28,25 +29,31 @@ class ValidateQueryMappingTaskTests(TestCase):
@mock.patch(
'license_manager.apps.subscriptions.management.commands.validate_num_catalog_queries.EnterpriseCatalogApiClient'
)
def test_email_sent_for_invalid_num_queries(self, is_valid, mock_api_client):
def test_error_logged_for_invalid_num_queries(self, is_valid, mock_api_client):
"""
Tests that an email is sent to ECS in the case that an invalid number of distinct
Tests that an error is logged in the case that an invalid number of distinct
CatalogQuery IDs are found to be used for all subscription customers.
Tests that no email is sent if the valid number of distinct CatalogQuery IDs are found.
"""
# Arbitrary number of subscriptions ("correct" number)
# Number of distinct subscription types ("correct" number)
num_subs = 3

for i in range(num_subs):
SubscriptionPlanFactory(product=ProductFactory(netsuite_id=i))
plan_type = PlanTypeFactory(internal_use_only=False, label=f'PlanType{i}')
SubscriptionPlanFactory(
product=ProductFactory(
netsuite_id=i,
plan_type=plan_type,
),
)

if is_valid:
log_level = 'INFO'
num_queries_found = num_subs
else:
log_level = 'ERROR'
num_queries_found = num_subs - 1
num_queries_found = num_subs + 1
with self.assertLogs(level=log_level) as log:
catalog_query_ids = {}
for _ in range(num_queries_found):
Expand All @@ -62,3 +69,41 @@ def test_email_sent_for_invalid_num_queries(self, is_valid, mock_api_client):
with self.assertRaises(InvalidCatalogQueryMappingError):
call_command(self.command_name)
assert 'ERROR' in log.output[0]

@mock.patch(
'license_manager.apps.subscriptions.management.commands.validate_num_catalog_queries.EnterpriseCatalogApiClient'
)
def test_allow_list_is_respected(self, mock_api_client):
"""
Tests that our configurable allow list of customized subscription catalogs
is respected by this command.
"""
# Number of distinct subscription types ("correct" number)
num_subs = 2
allowed_custom_catalog_uuid = str(uuid.uuid4())
allow_list = [allowed_custom_catalog_uuid]

for i in range(num_subs):
plan_type = PlanTypeFactory(internal_use_only=False, label=f'PlanType{i}')
SubscriptionPlanFactory(
product=ProductFactory(
netsuite_id=i,
plan_type=plan_type,
),
)

with self.assertLogs(level='INFO') as log, \
self.settings(CUSTOM_CATALOG_PRODUCTS_ALLOW_LIST=allow_list):
catalog_query_ids = {}
for index in range(num_subs):
catalog_query_ids[index] = [str(uuid.uuid4())]

# add one allowed *custom* catalog uuid to the response payload
catalog_query_ids[42] = allowed_custom_catalog_uuid

mock_api_client.return_value.get_distinct_catalog_queries.return_value = {
'num_distinct_query_ids': len(catalog_query_ids),
'catalog_uuids_by_catalog_query_id': catalog_query_ids,
}
call_command(self.command_name)
assert 'SUCCESS' in log.output[0]
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from collections import defaultdict

from django.conf import settings
from django.core.management.base import BaseCommand

from license_manager.apps.api_client.enterprise_catalog import (
Expand Down Expand Up @@ -39,7 +40,11 @@ def handle(self, *args, **options):
customer_subs = SubscriptionPlan.objects.filter(
expiration_processed=False,
for_internal_use_only=False,
).select_related('product')
product__plan_type__internal_use_only=False,
).select_related(
'product',
'product__plan_type',
)
distinct_catalog_uuids = [
str(uuid) for uuid in customer_subs.values_list('enterprise_catalog_uuid', flat=True).distinct()
]
Expand All @@ -51,21 +56,26 @@ def handle(self, *args, **options):
for catalog_uuid_batch in chunks(distinct_catalog_uuids, constants.VALIDATE_NUM_CATALOG_QUERIES_BATCH_SIZE):
response = EnterpriseCatalogApiClient().get_distinct_catalog_queries(catalog_uuid_batch)
query_ids = response['catalog_uuids_by_catalog_query_id']
for key in query_ids.keys():
catalog_uuids_by_catalog_query_id[key] += query_ids[key]
for catalog_query_id, catalog_uuid in query_ids.items():
if catalog_uuid not in settings.CUSTOM_CATALOG_PRODUCTS_ALLOW_LIST:
catalog_uuids_by_catalog_query_id[catalog_query_id] += catalog_uuid

distinct_catalog_query_ids = catalog_uuids_by_catalog_query_id.keys()
# Calculate the number of customer types using the distinct number of
# subscription Products found among customer subscriptions.
# non-internal-use-only Products-Plan Types found among customer subscriptions.
# If the number of distinct catalog
# query IDs doesn't match the number of customer types, log an error.
num_distinct_products = customer_subs.values_list('product__name', flat=True).distinct().count()
num_distinct_external_use_plan_types = customer_subs.values_list(
'product__plan_type__label',
flat=True,
).distinct().count()

summary = (
f'{len(distinct_catalog_query_ids)} distinct Subscription Catalog Queries found, '
f'{num_distinct_products} expected based on the number of distinct subscription products.'
f'{num_distinct_external_use_plan_types} expected based on the number of distinct subscription products.'
)

if len(distinct_catalog_query_ids) != num_distinct_products:
if len(distinct_catalog_query_ids) > num_distinct_external_use_plan_types:
# We typically only see a handful of catalogs that relate
# to some unaccounted-for catalog query, so we'll just log those,
# instead of logging the potentially thousands of catalog uuids
Expand Down
6 changes: 6 additions & 0 deletions license_manager/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,3 +462,9 @@
'license_manager.apps.api.utils.make_swagger_var_param_optional',
],
}

# An allow list of enterprise catalog uuids that excused from violations
# in the ``validate_num_catalog_queries`` management command
CUSTOM_CATALOG_PRODUCTS_ALLOW_LIST = [

]

0 comments on commit 490d99a

Please sign in to comment.