mirror of
https://github.com/ansible/awx.git
synced 2026-10-30 22:16:19 -02:30
Adjust subscription calculation & warning behavior
- Add a field for hosts automated across This is populated by the new table we've added. - Update the subs check to check against this, not imported hosts. - Reword messages on inventory import
This commit is contained in:
@@ -36,20 +36,20 @@ from awx.main.utils.pglock import advisory_lock
|
|||||||
logger = logging.getLogger('awx.main.commands.inventory_import')
|
logger = logging.getLogger('awx.main.commands.inventory_import')
|
||||||
|
|
||||||
LICENSE_EXPIRED_MESSAGE = '''\
|
LICENSE_EXPIRED_MESSAGE = '''\
|
||||||
License expired.
|
Subscription expired.
|
||||||
See http://www.ansible.com/renew for license extension information.'''
|
Contact us (https://www.redhat.com/contact) for subscription extension information.'''
|
||||||
|
|
||||||
LICENSE_NON_EXISTANT_MESSAGE = '''\
|
LICENSE_NON_EXISTANT_MESSAGE = '''\
|
||||||
No license.
|
No subscription.
|
||||||
See http://www.ansible.com/renew for license information.'''
|
Contact us (https://www.redhat.com/contact) for subscription information.'''
|
||||||
|
|
||||||
LICENSE_MESSAGE = '''\
|
LICENSE_MESSAGE = '''\
|
||||||
Number of licensed instances exceeded, would bring available instances to %(new_count)d, system is licensed for %(instance_count)d.
|
%(new_count)d instances have been automated, system is subscribed for %(instance_count)d.
|
||||||
See http://www.ansible.com/renew for license extension information.'''
|
Contact us (https://www.redhat.com/contact) for upgrade information.'''
|
||||||
|
|
||||||
DEMO_LICENSE_MESSAGE = '''\
|
DEMO_LICENSE_MESSAGE = '''\
|
||||||
Demo mode free license count exceeded, would bring available instances to %(new_count)d, demo mode allows %(instance_count)d.
|
Demo mode free subscription count exceeded. Current automated instances are %(new_count)d, demo mode allows %(instance_count)d.
|
||||||
See http://www.ansible.com/renew for licensing information.'''
|
Contact us (https://www.redhat.com/contact) for subscription information.'''
|
||||||
|
|
||||||
|
|
||||||
def functioning_dir(path):
|
def functioning_dir(path):
|
||||||
@@ -761,29 +761,22 @@ class Command(BaseCommand):
|
|||||||
instance_count = license_info.get('instance_count', 0)
|
instance_count = license_info.get('instance_count', 0)
|
||||||
free_instances = license_info.get('free_instances', 0)
|
free_instances = license_info.get('free_instances', 0)
|
||||||
time_remaining = license_info.get('time_remaining', 0)
|
time_remaining = license_info.get('time_remaining', 0)
|
||||||
|
automated_count = license_info.get('automated_instances', 0)
|
||||||
hard_error = license_info.get('trial', False) is True or license_info['instance_count'] == 10
|
hard_error = license_info.get('trial', False) is True or license_info['instance_count'] == 10
|
||||||
new_count = Host.objects.active_count()
|
|
||||||
if time_remaining <= 0:
|
if time_remaining <= 0:
|
||||||
if hard_error:
|
if hard_error:
|
||||||
logger.error(LICENSE_EXPIRED_MESSAGE)
|
logger.error(LICENSE_EXPIRED_MESSAGE)
|
||||||
raise PermissionDenied("License has expired!")
|
raise PermissionDenied("Subscription has expired!")
|
||||||
else:
|
else:
|
||||||
logger.warning(LICENSE_EXPIRED_MESSAGE)
|
logger.warning(LICENSE_EXPIRED_MESSAGE)
|
||||||
# special check for tower-type inventory sources
|
|
||||||
# but only if running the plugin
|
|
||||||
TOWER_SOURCE_FILES = ['tower.yml', 'tower.yaml']
|
|
||||||
if self.inventory_source.source == 'tower' and any(f in self.inventory_source.source_path for f in TOWER_SOURCE_FILES):
|
|
||||||
# only if this is the 2nd call to license check, we cannot compare before running plugin
|
|
||||||
if hasattr(self, 'all_group'):
|
|
||||||
self.remote_tower_license_compare(local_license_type)
|
|
||||||
if free_instances < 0:
|
if free_instances < 0:
|
||||||
d = {
|
d = {
|
||||||
'new_count': new_count,
|
'new_count': automated_count,
|
||||||
'instance_count': instance_count,
|
'instance_count': instance_count,
|
||||||
}
|
}
|
||||||
if hard_error:
|
if hard_error:
|
||||||
logger.error(LICENSE_MESSAGE % d)
|
logger.error(LICENSE_MESSAGE % d)
|
||||||
raise PermissionDenied('License count exceeded!')
|
raise PermissionDenied('Subscription count exceeded!')
|
||||||
else:
|
else:
|
||||||
logger.warning(LICENSE_MESSAGE % d)
|
logger.warning(LICENSE_MESSAGE % d)
|
||||||
|
|
||||||
|
|||||||
@@ -552,7 +552,7 @@ class JobEvent(BasePlaybookEvent):
|
|||||||
summaries = dict()
|
summaries = dict()
|
||||||
updated_hosts_list = list()
|
updated_hosts_list = list()
|
||||||
for host in hostnames:
|
for host in hostnames:
|
||||||
updated_hosts_list.append(host)
|
updated_hosts_list.append(host.lower())
|
||||||
host_id = self.host_map.get(host, None)
|
host_id = self.host_map.get(host, None)
|
||||||
if host_id not in existing_host_ids:
|
if host_id not in existing_host_ids:
|
||||||
host_id = None
|
host_id = None
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from django.conf import settings
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import Host
|
from awx.main.models import Host, HostMetric, Instance
|
||||||
|
|
||||||
MAX_INSTANCES = 9999999
|
MAX_INSTANCES = 9999999
|
||||||
|
|
||||||
@@ -383,12 +383,20 @@ class Licenser(object):
|
|||||||
current_instances = Host.objects.active_count()
|
current_instances = Host.objects.active_count()
|
||||||
else:
|
else:
|
||||||
current_instances = 0
|
current_instances = 0
|
||||||
|
license_date = int(attrs.get('license_date', 0) or 0)
|
||||||
|
automated_instances = HostMetric.objects.count()
|
||||||
|
first_host = HostMetric.objects.only('first_automation').order_by('first_automation').first()
|
||||||
|
if first_host:
|
||||||
|
automated_since = int(first_host.first_automation.timestamp())
|
||||||
|
else:
|
||||||
|
automated_since = int(Instance.objects.order_by('id').first().created.timestamp())
|
||||||
instance_count = int(attrs.get('instance_count', 0))
|
instance_count = int(attrs.get('instance_count', 0))
|
||||||
attrs['current_instances'] = current_instances
|
attrs['current_instances'] = current_instances
|
||||||
free_instances = instance_count - current_instances
|
attrs['automated_instances'] = automated_instances
|
||||||
|
attrs['automated_since'] = automated_since
|
||||||
|
free_instances = instance_count - automated_instances
|
||||||
attrs['free_instances'] = max(0, free_instances)
|
attrs['free_instances'] = max(0, free_instances)
|
||||||
|
|
||||||
license_date = int(attrs.get('license_date', 0) or 0)
|
|
||||||
current_date = int(time.time())
|
current_date = int(time.time())
|
||||||
time_remaining = license_date - current_date
|
time_remaining = license_date - current_date
|
||||||
attrs['time_remaining'] = time_remaining
|
attrs['time_remaining'] = time_remaining
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { node, string } from 'prop-types';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { formatDateString } from '../../util/dates';
|
||||||
|
import _Detail from './Detail';
|
||||||
|
|
||||||
|
const Detail = styled(_Detail)`
|
||||||
|
word-break: break-word;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function NumberSinceDetail({ label, number, date, dataCy = null }) {
|
||||||
|
const dateStr = formatDateString(date);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Detail
|
||||||
|
label={label}
|
||||||
|
dataCy={dataCy}
|
||||||
|
value={t`${number} since ${dateStr}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
NumberSinceDetail.propTypes = {
|
||||||
|
label: node.isRequired,
|
||||||
|
number: string.isRequired,
|
||||||
|
date: string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NumberSinceDetail;
|
||||||
@@ -5,6 +5,7 @@ export { default as UserDateDetail } from './UserDateDetail';
|
|||||||
export { default as DetailBadge } from './DetailBadge';
|
export { default as DetailBadge } from './DetailBadge';
|
||||||
export { default as ArrayDetail } from './ArrayDetail';
|
export { default as ArrayDetail } from './ArrayDetail';
|
||||||
export { default as LaunchedByDetail } from './LaunchedByDetail';
|
export { default as LaunchedByDetail } from './LaunchedByDetail';
|
||||||
|
export { default as NumberSinceDetail } from './NumberSinceDetail';
|
||||||
/*
|
/*
|
||||||
NOTE: CodeDetail cannot be imported here, as it causes circular
|
NOTE: CodeDetail cannot be imported here, as it causes circular
|
||||||
dependencies in testing environment. Import it directly from
|
dependencies in testing environment. Import it directly from
|
||||||
|
|||||||
+1073
-1024
File diff suppressed because it is too large
Load Diff
+1073
-1024
File diff suppressed because it is too large
Load Diff
+1073
-1024
File diff suppressed because it is too large
Load Diff
+1073
-1024
File diff suppressed because it is too large
Load Diff
+1073
-1024
File diff suppressed because it is too large
Load Diff
+1073
-1024
File diff suppressed because it is too large
Load Diff
+1073
-1024
File diff suppressed because it is too large
Load Diff
+18
-3
@@ -10,7 +10,11 @@ import {
|
|||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
import RoutedTabs from '../../../../components/RoutedTabs';
|
import RoutedTabs from '../../../../components/RoutedTabs';
|
||||||
import { CardBody, CardActionsRow } from '../../../../components/Card';
|
import { CardBody, CardActionsRow } from '../../../../components/Card';
|
||||||
import { DetailList, Detail } from '../../../../components/DetailList';
|
import {
|
||||||
|
DetailList,
|
||||||
|
Detail,
|
||||||
|
NumberSinceDetail,
|
||||||
|
} from '../../../../components/DetailList';
|
||||||
import { useConfig } from '../../../../contexts/Config';
|
import { useConfig } from '../../../../contexts/Config';
|
||||||
import {
|
import {
|
||||||
formatDateString,
|
formatDateString,
|
||||||
@@ -126,10 +130,21 @@ function SubscriptionDetail() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Detail
|
<Detail
|
||||||
dataCy="subscription-hosts-used"
|
dataCy="subscription-hosts-imported"
|
||||||
label={t`Hosts used`}
|
label={t`Hosts imported`}
|
||||||
value={license_info.current_instances}
|
value={license_info.current_instances}
|
||||||
/>
|
/>
|
||||||
|
<NumberSinceDetail
|
||||||
|
dataCy="subscription-hosts-automated"
|
||||||
|
label={t`Hosts automated`}
|
||||||
|
number={license_info.automated_instances}
|
||||||
|
date={
|
||||||
|
license_info.automated_since &&
|
||||||
|
formatDateString(
|
||||||
|
new Date(license_info.automated_since * 1000).toISOString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Detail
|
<Detail
|
||||||
dataCy="subscription-hosts-remaining"
|
dataCy="subscription-hosts-remaining"
|
||||||
label={t`Hosts remaining`}
|
label={t`Hosts remaining`}
|
||||||
|
|||||||
+4
-1
@@ -14,6 +14,8 @@ const config = {
|
|||||||
date_expired: false,
|
date_expired: false,
|
||||||
date_warning: true,
|
date_warning: true,
|
||||||
free_instances: 1000,
|
free_instances: 1000,
|
||||||
|
automated_instances: '12',
|
||||||
|
automated_since: '1614714228',
|
||||||
grace_period_remaining: 2904229,
|
grace_period_remaining: 2904229,
|
||||||
instance_count: 1001,
|
instance_count: 1001,
|
||||||
license_date: '1614401999',
|
license_date: '1614401999',
|
||||||
@@ -65,8 +67,9 @@ describe('<SubscriptionDetail />', () => {
|
|||||||
assertDetail('Trial', 'False');
|
assertDetail('Trial', 'False');
|
||||||
assertDetail('Expires on', '2/27/2021, 4:59:59 AM');
|
assertDetail('Expires on', '2/27/2021, 4:59:59 AM');
|
||||||
assertDetail('Days remaining', '3');
|
assertDetail('Days remaining', '3');
|
||||||
assertDetail('Hosts used', '1');
|
assertDetail('Hosts imported', '1');
|
||||||
assertDetail('Hosts remaining', '1000');
|
assertDetail('Hosts remaining', '1000');
|
||||||
|
assertDetail('Hosts automated', '12 since 3/2/2021, 7:43:48 PM');
|
||||||
|
|
||||||
expect(wrapper.find('Button[aria-label="edit"]').length).toBe(1);
|
expect(wrapper.find('Button[aria-label="edit"]').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user