From 52db0bf0c01640292cc6da9a3c8a78f67b57a273 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Tue, 24 Nov 2020 15:37:15 -0500 Subject: [PATCH] Allow combining multiple subscriptions if they are in a manifest. The rules: - subs must be of same SKU type (all MCTxxxx, all SERxxxx, etc) - when combining, we take the earliest end date --- awx/main/utils/licensing.py | 76 +++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/awx/main/utils/licensing.py b/awx/main/utils/licensing.py index efefcf6944..3956460337 100644 --- a/awx/main/utils/licensing.py +++ b/awx/main/utils/licensing.py @@ -11,7 +11,7 @@ The Licenser class can do the following: import base64 import configparser -from datetime import datetime +from datetime import datetime, timezone import collections import copy import io @@ -73,9 +73,12 @@ def validate_entitlement_manifest(data): buff.write(export) z = zipfile.ZipFile(buff) + subs = [] for f in z.filelist: if f.filename.startswith('export/entitlements') and f.filename.endswith('.json'): - return json.loads(z.open(f).read()) + subs.append(json.loads(z.open(f).read())) + if subs: + return subs raise ValueError(_("Invalid manifest: manifest contains no subscriptions.")) @@ -131,21 +134,64 @@ class Licenser(object): def license_from_manifest(self, manifest): + def is_appropriate_manifest_sub(sub): + if sub['pool']['activeSubscription'] is False: + return False + now = datetime.now(timezone.utc) + if parse_date(sub['startDate']) > now: + return False + if parse_date(sub['endDate']) < now: + return False + products = sub['pool']['providedProducts'] + if any(product.get('productId') == '480' for product in products): + return True + return False + + def _can_aggregate(sub, license): + # We aggregate multiple subs into a larger meta-sub, if they match + # + # No current sub in aggregate + if not license: + return True + # Same SKU type (SER vs MCT vs others)? + if license['sku'][0:3] != sub['pool']['productId'][0:3]: + return False + return True + # Parse output for subscription metadata to build config license = dict() - license['sku'] = manifest['pool']['productId'] - try: - license['instance_count'] = manifest['pool']['exported'] - except KeyError: - license['instance_count'] = manifest['pool']['quantity'] - license['subscription_name'] = manifest['pool']['productName'] - license['pool_id'] = manifest['pool']['id'] - license['license_date'] = parse_date(manifest['endDate']).strftime('%s') - license['product_name'] = manifest['pool']['productName'] - license['valid_key'] = True - license['license_type'] = 'enterprise' - license['satellite'] = False + for sub in manifest: + if not is_appropriate_manifest_sub(sub): + logger.warning("Subscription %s (%s) in manifest is not active or for another product" % + (sub['pool']['productName'], sub['pool']['productId'])) + continue + if not _can_aggregate(sub, license): + logger.warning("Subscription %s (%s) in manifest does not match other manifest subscriptions" % + (sub['pool']['productName'], sub['pool']['productId'])) + continue + license.setdefault('sku', sub['pool']['productId']) + license.setdefault('subscription_name', sub['pool']['productName']) + license.setdefault('pool_id', sub['pool']['id']) + license.setdefault('product_name', sub['pool']['productName']) + license.setdefault('valid_key', True) + license.setdefault('license_type', 'enterprise') + license.setdefault('satellite', False) + # Use the nearest end date + endDate = parse_date(sub['endDate']) + currentEndDateStr = license.get('license_date', '4102462800') # 2100-01-01 + currentEndDate = datetime.fromtimestamp(int(currentEndDateStr), timezone.utc) + if endDate < currentEndDate: + license['license_date'] = endDate.strftime('%s') + try: + instances = sub['pool']['exported'] + except KeyError: + instances = sub['pool']['quantity'] + license['instance_count'] = license.get('instance_count', 0) + instances + license['subscription_name'] = re.sub(r'[\d]* Managed Nodes', '%d Managed Nodes' % license['instance_count'], license['subscription_name']) + + if not license: + logger.error("No valid subscriptions found in manifest") self._attrs.update(license) settings.LICENSE = self._attrs return self._attrs @@ -272,7 +318,7 @@ class Licenser(object): return False # Products that contain Ansible Tower products = sub.get('providedProducts', []) - if any(map(lambda product: product.get('productId', None) == "480", products)): + if any(product.get('productId') == '480' for product in products): return True return False