Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ascender/api/serializers/bulk.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def raise_if_host_counts_violated(self, attrs):
if org:
org_active_count = Host.objects.org_active_count(org.id)
new_hosts = [h['name'] for h in attrs['hosts']]
org_net_new_host_count = len(new_hosts) - Host.objects.filter(inventory__organization=1, name__in=new_hosts).values('name').distinct().count()
org_net_new_host_count = len(new_hosts) - Host.objects.filter(inventory__organization=org.id, name__in=new_hosts).values('name').distinct().count()
if org.max_hosts > 0 and org_active_count + org_net_new_host_count > org.max_hosts:
raise PermissionDenied(
_(
Expand Down
2 changes: 1 addition & 1 deletion ascender/api/serializers/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,5 +301,5 @@ class Meta:
class HostMetricSummaryMonthlySerializer(BaseSerializer):
class Meta:
model = HostMetricSummaryMonthly
read_only_fields = ("id", "date", "license_consumed", "license_capacity", "hosts_added", "hosts_deleted", "indirectly_managed_hosts")
read_only_fields = ("id", "date", "hosts_added", "hosts_deleted", "indirectly_managed_hosts")
fields = read_only_fields
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Make a GET request to this resource to retrieve aggregate statistics about inventory suitable for graphing.

Including fetching the number of total hosts tracked by Tower over an amount of time and the current success or
Including fetching the number of total hosts tracked by Ascender over an amount of time and the current success or
failed status of hosts which have run jobs within an Inventory.

## Parameters and Filtering
Expand Down
2 changes: 1 addition & 1 deletion ascender/api/templates/api/dashboard_view.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Make a GET request to this resource to retrieve aggregate statistics for Tower.
Make a GET request to this resource to retrieve aggregate statistics for Ascender.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def setting_names():
Every setting name the settings object answers to.

dir() is not enough on its own: a setting that only the conf registry
provides, CUSTOM_LOGO and LICENSE among them, answers to getattr without
provides, CUSTOM_LOGO among them, answers to getattr without
appearing there, so the registry is unioned in. Leaving them out is how the
first version of this file missed eight settings that views read.
"""
Expand Down
13 changes: 13 additions & 0 deletions ascender/conf/migrations/0015_remove_subscription_usage_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.db import migrations


def remove_subscription_usage_model(apps, schema_editor):
"""Its only effect was hiding Host Metrics behind a subscription concept."""
Setting = apps.get_model('conf', 'Setting')
Setting.objects.filter(key='SUBSCRIPTION_USAGE_MODEL').delete()


class Migration(migrations.Migration):
dependencies = [('conf', '0014_remove_subscription_settings')]

operations = [migrations.RunPython(remove_subscription_usage_model, migrations.RunPython.noop)]
18 changes: 0 additions & 18 deletions ascender/main/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# Ascender
from ascender.conf import fields, register, register_validate
from ascender.main.models import ExecutionEnvironment
from ascender.main.constants import SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS

logger = logging.getLogger('awx.main.conf')

Expand Down Expand Up @@ -757,23 +756,6 @@
category_slug='bulk',
)

register(
'SUBSCRIPTION_USAGE_MODEL',
field_class=fields.ChoiceField,
choices=[
('', _('Default model for Ascender - no subscription. Deletion of host_metrics will not be considered for purposes of managed host counting')),
(
SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS,
_('Usage based on unique managed nodes in a large historical time frame and delete functionality for no longer used managed nodes'),
),
],
default='',
allow_blank=True,
label=_('Defines subscription usage model and shows Host Metrics'),
category=_('System'),
category_slug='system',
)

register(
'CLEANUP_HOST_METRICS_LAST_TS',
field_class=fields.DateTimeField,
Expand Down
3 changes: 0 additions & 3 deletions ascender/main/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,5 @@
'\u001b[31m \u001b[1m This can be caused if the version of ansible-runner in your execution environment is out of date.\u001b[0m'
)

# Values for setting SUBSCRIPTION_USAGE_MODEL
SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS = 'unique_managed_hosts'

# Shared prefetch to use for creating a queryset for the purpose of writing or saving facts
HOST_FACTS_FIELDS = ('name', 'ansible_facts', 'ansible_facts_modified', 'modified', 'inventory_id')
2 changes: 0 additions & 2 deletions ascender/main/management/commands/host_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ def host_metric_summary_monthly_queryset(self, result, offset=0, limit=BATCHED_F
result.values(
'id',
'date',
'license_consumed',
'license_capacity',
'hosts_added',
'hosts_deleted',
'indirectly_managed_hosts',
Expand Down
2 changes: 0 additions & 2 deletions ascender/main/management/commands/inventory_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
from django.db import connection, transaction
from django.utils.encoding import smart_str

# DRF error class to distinguish license exceptions

# Ascender inventory imports
from ascender.main.models.inventory import Inventory, InventorySource, InventoryUpdate
from ascender.main.models.jobs import JobHostSummary
Expand Down
2 changes: 1 addition & 1 deletion ascender/main/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class HostManager(models.Manager.from_queryset(HostLatestSummaryQuerySet)):
"""Custom manager class for Hosts model."""

def active_count(self):
"""Return count of active, unique hosts for licensing.
"""Return count of active, unique hosts, which host metrics reports.
Construction of query involves:
- remove any ordering specified in model's Meta
- Exclude hosts sourced from another Ascender
Expand Down
44 changes: 44 additions & 0 deletions ascender/main/migrations/0217_drop_host_metric_licence_columns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 6.1.1 on 2026-09-15 23:27

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("main", "0216_rename_towerschedulestate"),
]

operations = [
migrations.RemoveField(
model_name="hostmetricsummarymonthly",
name="license_capacity",
),
migrations.RemoveField(
model_name="hostmetricsummarymonthly",
name="license_consumed",
),
migrations.AlterField(
model_name="hostmetric",
name="deleted",
field=models.BooleanField(
default=False,
help_text="Boolean flag saying whether the host is deleted and therefore no longer counted as automated",
),
),
migrations.AlterField(
model_name="hostmetricsummarymonthly",
name="hosts_added",
field=models.IntegerField(
default=0,
help_text="How many hosts were first automated in the associated month",
),
),
migrations.AlterField(
model_name="hostmetricsummarymonthly",
name="hosts_deleted",
field=models.IntegerField(
default=0,
help_text="How many hosts were deleted in the associated month",
),
),
]
10 changes: 3 additions & 7 deletions ascender/main/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,9 +900,7 @@ class HostMetric(models.Model):
last_deleted = models.DateTimeField(null=True, db_index=True, help_text=_('When the host was last deleted'))
automated_counter = models.BigIntegerField(default=0, help_text=_('How many times was the host automated'))
deleted_counter = models.IntegerField(default=0, help_text=_('How many times was the host deleted'))
deleted = models.BooleanField(
default=False, help_text=_('Boolean flag saying whether the host is deleted and therefore not counted into the subscription consumption')
)
deleted = models.BooleanField(default=False, help_text=_('Boolean flag saying whether the host is deleted and therefore no longer counted as automated'))
used_in_inventories = models.IntegerField(null=True, help_text=_('How many inventories contain this host'))

objects = models.Manager()
Expand Down Expand Up @@ -930,10 +928,8 @@ class HostMetricSummaryMonthly(models.Model):
"""

date = models.DateField(unique=True)
license_consumed = models.BigIntegerField(default=0, help_text=_("How many unique hosts are consumed from the license"))
license_capacity = models.BigIntegerField(default=0, help_text=_("'License capacity as max. number of unique hosts"))
hosts_added = models.IntegerField(default=0, help_text=_("How many hosts were added in the associated month, consuming more license capacity"))
hosts_deleted = models.IntegerField(default=0, help_text=_("How many hosts were deleted in the associated month, freeing the license capacity"))
hosts_added = models.IntegerField(default=0, help_text=_("How many hosts were first automated in the associated month"))
hosts_deleted = models.IntegerField(default=0, help_text=_("How many hosts were deleted in the associated month"))
indirectly_managed_hosts = models.IntegerField(default=0, help_text=("Manually entered number indirectly managed hosts for a certain month"))


Expand Down
38 changes: 5 additions & 33 deletions ascender/main/tasks/host_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,6 @@ class HostMetricSummaryMonthlyTask:
- hosts_added are HostMetric records with first_automation in given month
- hosts_deleted are HostMetric records with deleted=True and last_deleted in given month
- - HostMetrics soft-deleted before <threshold> also increases hosts_deleted in their last_deleted month
- license_consumed is license_consumed(previous month) + hosts_added - hosts_deleted
- - license_consumed for HostMetricSummaryMonthly.date < [threshold] is computed also from
all HostMetrics.first_automation < [threshold]
- license_capacity is set only for current month, and it's never updated (value taken from current subscription)
"""

def __init__(self):
Expand All @@ -119,38 +115,20 @@ def execute(self):
self._load_hosts_added()
self._load_hosts_deleted()

# Get first month after last hard delete
month = self._get_first_month()
license_consumed = self._get_license_consumed_before(month)

# Fill record for each month
month = self._get_first_month()
while month <= datetime.date.today().replace(day=1):
summary = self._find_or_create_summary(month)
# Update summary and update license_consumed by hosts added/removed this month
self._update_summary(summary, month, license_consumed)
license_consumed = summary.license_consumed

self._update_summary(self._find_or_create_summary(month), month)
month = month + relativedelta(months=1)

# Create/Update stats
HostMetricSummaryMonthly.objects.bulk_create(self.records_to_create)

bulk_update_sorted_by_id(HostMetricSummaryMonthly, self.records_to_update, ['license_consumed', 'hosts_added', 'hosts_deleted'])
bulk_update_sorted_by_id(HostMetricSummaryMonthly, self.records_to_update, ['hosts_added', 'hosts_deleted'])

# Set timestamp of last run
settings.HOST_METRIC_SUMMARY_TASK_LAST_TS = now()

def _get_license_consumed_before(self, month):
license_consumed = 0
for metric_month, metric in self.host_metrics.items():
if metric_month < month:
hosts_added = metric.get('hosts_added', 0)
hosts_deleted = metric.get('hosts_deleted', 0)
license_consumed = license_consumed + hosts_added - hosts_deleted
else:
break
return license_consumed

def _load_existing_summaries(self):
"""Find all summaries newer than host metrics delete threshold"""
self.existing_summaries = HostMetricSummaryMonthly.objects.filter(date__gte=self._get_first_month()).order_by('date')
Expand Down Expand Up @@ -238,22 +216,16 @@ def _find_summary(self, month):
break
return summary

def _update_summary(self, summary, month, license_consumed):
"""Updates the metric with hosts added and deleted and set license info for current month"""
def _update_summary(self, summary, month):
"""Record how many hosts were first automated and deleted in the month"""
# Get month counts from host metrics, zero if not found
hosts_added, hosts_deleted = 0, 0
if metric := self.host_metrics.get(month, None):
hosts_added = metric.get('hosts_added', 0)
hosts_deleted = metric.get('hosts_deleted', 0)

summary.license_consumed = license_consumed + hosts_added - hosts_deleted
summary.hosts_added = hosts_added
summary.hosts_deleted = hosts_deleted

# license_capacity counted against a subscription. There is none, so the
# column keeps the zero the subscription branch already produced.
if month == datetime.date.today().replace(day=1):
summary.license_capacity = 0
return summary

@staticmethod
Expand Down
1 change: 0 additions & 1 deletion ascender/main/tasks/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1350,7 +1350,6 @@ def build_extra_vars_file(self, project_update, private_data_dir):
'projects_root': settings.PROJECTS_ROOT.rstrip('/'),
'local_path': os.path.basename(project_update.project.local_path),
'project_path': project_update.get_project_path(check_if_exists=False), # deprecated
'awx_license_type': 'open',
'awx_version': get_ascender_version(),
'scm_url': scm_url,
'scm_branch': scm_branch,
Expand Down
4 changes: 0 additions & 4 deletions ascender/main/tests/data/inventory/plugins/insights/env.json

This file was deleted.

4 changes: 1 addition & 3 deletions ascender/main/tests/factories/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,11 +279,9 @@ def mk_host_metric(hostname, first_automation, last_automation=None, last_delete
raise e


def mk_host_metric_summary(date, license_consumed=0, license_capacity=0, hosts_added=0, hosts_deleted=0, indirectly_managed_hosts=0, persisted=True):
def mk_host_metric_summary(date, hosts_added=0, hosts_deleted=0, indirectly_managed_hosts=0, persisted=True):
summary = HostMetricSummaryMonthly(
date=date,
license_consumed=license_consumed,
license_capacity=license_capacity,
hosts_added=hosts_added,
hosts_deleted=hosts_deleted,
indirectly_managed_hosts=indirectly_managed_hosts,
Expand Down
1 change: 0 additions & 1 deletion ascender/main/tests/functional/api/test_survey_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def test_survey_edit_access(job_template, workflow_job_template, survey_spec_fac
)


# Test normal operations with survey license work
@pytest.mark.django_db
@pytest.mark.survey
def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user):
Expand Down
Loading