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')
|
||||
|
||||
LICENSE_EXPIRED_MESSAGE = '''\
|
||||
License expired.
|
||||
See http://www.ansible.com/renew for license extension information.'''
|
||||
Subscription expired.
|
||||
Contact us (https://www.redhat.com/contact) for subscription extension information.'''
|
||||
|
||||
LICENSE_NON_EXISTANT_MESSAGE = '''\
|
||||
No license.
|
||||
See http://www.ansible.com/renew for license information.'''
|
||||
No subscription.
|
||||
Contact us (https://www.redhat.com/contact) for subscription information.'''
|
||||
|
||||
LICENSE_MESSAGE = '''\
|
||||
Number of licensed instances exceeded, would bring available instances to %(new_count)d, system is licensed for %(instance_count)d.
|
||||
See http://www.ansible.com/renew for license extension information.'''
|
||||
%(new_count)d instances have been automated, system is subscribed for %(instance_count)d.
|
||||
Contact us (https://www.redhat.com/contact) for upgrade information.'''
|
||||
|
||||
DEMO_LICENSE_MESSAGE = '''\
|
||||
Demo mode free license count exceeded, would bring available instances to %(new_count)d, demo mode allows %(instance_count)d.
|
||||
See http://www.ansible.com/renew for licensing information.'''
|
||||
Demo mode free subscription count exceeded. Current automated instances are %(new_count)d, demo mode allows %(instance_count)d.
|
||||
Contact us (https://www.redhat.com/contact) for subscription information.'''
|
||||
|
||||
|
||||
def functioning_dir(path):
|
||||
@@ -761,29 +761,22 @@ class Command(BaseCommand):
|
||||
instance_count = license_info.get('instance_count', 0)
|
||||
free_instances = license_info.get('free_instances', 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
|
||||
new_count = Host.objects.active_count()
|
||||
if time_remaining <= 0:
|
||||
if hard_error:
|
||||
logger.error(LICENSE_EXPIRED_MESSAGE)
|
||||
raise PermissionDenied("License has expired!")
|
||||
raise PermissionDenied("Subscription has expired!")
|
||||
else:
|
||||
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:
|
||||
d = {
|
||||
'new_count': new_count,
|
||||
'new_count': automated_count,
|
||||
'instance_count': instance_count,
|
||||
}
|
||||
if hard_error:
|
||||
logger.error(LICENSE_MESSAGE % d)
|
||||
raise PermissionDenied('License count exceeded!')
|
||||
raise PermissionDenied('Subscription count exceeded!')
|
||||
else:
|
||||
logger.warning(LICENSE_MESSAGE % d)
|
||||
|
||||
|
||||
@@ -552,7 +552,7 @@ class JobEvent(BasePlaybookEvent):
|
||||
summaries = dict()
|
||||
updated_hosts_list = list()
|
||||
for host in hostnames:
|
||||
updated_hosts_list.append(host)
|
||||
updated_hosts_list.append(host.lower())
|
||||
host_id = self.host_map.get(host, None)
|
||||
if host_id not in existing_host_ids:
|
||||
host_id = None
|
||||
|
||||
@@ -35,7 +35,7 @@ from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# AWX
|
||||
from awx.main.models import Host
|
||||
from awx.main.models import Host, HostMetric, Instance
|
||||
|
||||
MAX_INSTANCES = 9999999
|
||||
|
||||
@@ -383,12 +383,20 @@ class Licenser(object):
|
||||
current_instances = Host.objects.active_count()
|
||||
else:
|
||||
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))
|
||||
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)
|
||||
|
||||
license_date = int(attrs.get('license_date', 0) or 0)
|
||||
current_date = int(time.time())
|
||||
time_remaining = license_date - current_date
|
||||
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 ArrayDetail } from './ArrayDetail';
|
||||
export { default as LaunchedByDetail } from './LaunchedByDetail';
|
||||
export { default as NumberSinceDetail } from './NumberSinceDetail';
|
||||
/*
|
||||
NOTE: CodeDetail cannot be imported here, as it causes circular
|
||||
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';
|
||||
import RoutedTabs from '../../../../components/RoutedTabs';
|
||||
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 {
|
||||
formatDateString,
|
||||
@@ -126,10 +130,21 @@ function SubscriptionDetail() {
|
||||
/>
|
||||
)}
|
||||
<Detail
|
||||
dataCy="subscription-hosts-used"
|
||||
label={t`Hosts used`}
|
||||
dataCy="subscription-hosts-imported"
|
||||
label={t`Hosts imported`}
|
||||
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
|
||||
dataCy="subscription-hosts-remaining"
|
||||
label={t`Hosts remaining`}
|
||||
|
||||
+4
-1
@@ -14,6 +14,8 @@ const config = {
|
||||
date_expired: false,
|
||||
date_warning: true,
|
||||
free_instances: 1000,
|
||||
automated_instances: '12',
|
||||
automated_since: '1614714228',
|
||||
grace_period_remaining: 2904229,
|
||||
instance_count: 1001,
|
||||
license_date: '1614401999',
|
||||
@@ -65,8 +67,9 @@ describe('<SubscriptionDetail />', () => {
|
||||
assertDetail('Trial', 'False');
|
||||
assertDetail('Expires on', '2/27/2021, 4:59:59 AM');
|
||||
assertDetail('Days remaining', '3');
|
||||
assertDetail('Hosts used', '1');
|
||||
assertDetail('Hosts imported', '1');
|
||||
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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user