mirror of
https://github.com/ansible/awx.git
synced 2026-03-09 21:49:27 -02:30
Add smart_inventories endpoint to Host
This commit is contained in:
@@ -1204,6 +1204,7 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
job_host_summaries = self.reverse('api:host_job_host_summaries_list', kwargs={'pk': obj.pk}),
|
job_host_summaries = self.reverse('api:host_job_host_summaries_list', kwargs={'pk': obj.pk}),
|
||||||
activity_stream = self.reverse('api:host_activity_stream_list', kwargs={'pk': obj.pk}),
|
activity_stream = self.reverse('api:host_activity_stream_list', kwargs={'pk': obj.pk}),
|
||||||
inventory_sources = self.reverse('api:host_inventory_sources_list', kwargs={'pk': obj.pk}),
|
inventory_sources = self.reverse('api:host_inventory_sources_list', kwargs={'pk': obj.pk}),
|
||||||
|
smart_inventories = self.reverse('api:host_smart_inventories_list', kwargs={'pk': obj.pk}),
|
||||||
ad_hoc_commands = self.reverse('api:host_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
|
ad_hoc_commands = self.reverse('api:host_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
|
||||||
ad_hoc_command_events = self.reverse('api:host_ad_hoc_command_events_list', kwargs={'pk': obj.pk}),
|
ad_hoc_command_events = self.reverse('api:host_ad_hoc_command_events_list', kwargs={'pk': obj.pk}),
|
||||||
fact_versions = self.reverse('api:host_fact_versions_list', kwargs={'pk': obj.pk}),
|
fact_versions = self.reverse('api:host_fact_versions_list', kwargs={'pk': obj.pk}),
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ host_urls = patterns('awx.api.views',
|
|||||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'host_job_host_summaries_list'),
|
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'host_job_host_summaries_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'host_activity_stream_list'),
|
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'host_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'host_inventory_sources_list'),
|
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'host_inventory_sources_list'),
|
||||||
|
url(r'^(?P<pk>[0-9]+)/smart_inventories/$', 'host_smart_inventories_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', 'host_ad_hoc_commands_list'),
|
url(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', 'host_ad_hoc_commands_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_command_events/$', 'host_ad_hoc_command_events_list'),
|
url(r'^(?P<pk>[0-9]+)/ad_hoc_command_events/$', 'host_ad_hoc_command_events_list'),
|
||||||
#url(r'^(?P<pk>[0-9]+)/single_fact/$', 'host_single_fact_view'),
|
#url(r'^(?P<pk>[0-9]+)/single_fact/$', 'host_single_fact_view'),
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import ansiconv
|
|||||||
from social.backends.utils import load_backends
|
from social.backends.utils import load_backends
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.tasks import send_notifications
|
from awx.main.tasks import send_notifications, update_host_smart_inventory_memberships
|
||||||
from awx.main.access import get_user_queryset
|
from awx.main.access import get_user_queryset
|
||||||
from awx.main.ha import is_ha_environment
|
from awx.main.ha import is_ha_environment
|
||||||
from awx.api.authentication import TaskAuthentication, TokenGetAuthentication
|
from awx.api.authentication import TaskAuthentication, TokenGetAuthentication
|
||||||
@@ -2006,6 +2006,22 @@ class HostInventorySourcesList(SubListAPIView):
|
|||||||
new_in_148 = True
|
new_in_148 = True
|
||||||
|
|
||||||
|
|
||||||
|
class HostSmartInventoriesList(SubListAPIView):
|
||||||
|
model = Inventory
|
||||||
|
serializer_class = InventorySerializer
|
||||||
|
parent_model = Host
|
||||||
|
relationship = 'smart_inventories'
|
||||||
|
new_in_320 = True
|
||||||
|
|
||||||
|
def list(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
if settings.AWX_REBUILD_SMART_MEMBERSHIP:
|
||||||
|
update_host_smart_inventory_memberships.delay()
|
||||||
|
return super(HostSmartInventoriesList, self).list(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
return Response(dict(error=_(unicode(e))), status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class HostActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView):
|
class HostActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView):
|
||||||
|
|
||||||
model = ActivityStream
|
model = ActivityStream
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class Migration(migrations.Migration):
|
|||||||
name='inventory',
|
name='inventory',
|
||||||
field=models.ForeignKey(related_name='inventory_sources', default=None, to='main.Inventory', null=True),
|
field=models.ForeignKey(related_name='inventory_sources', default=None, to='main.Inventory', null=True),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# Smart Inventory
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='inventory',
|
model_name='inventory',
|
||||||
name='host_filter',
|
name='host_filter',
|
||||||
@@ -44,7 +46,28 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='inventory',
|
model_name='inventory',
|
||||||
name='kind',
|
name='kind',
|
||||||
field=models.CharField(default=b'', help_text='Kind of inventory being represented.', max_length=32, choices=[(b'', 'Hosts have a direct link to this inventory.'), (b'smart', 'Hosts for inventory generated using the host_filter property.')]),
|
field=models.CharField(default=b'', help_text='Kind of inventory being represented.', max_length=32, blank=True, choices=[(b'', 'Hosts have a direct link to this inventory.'), (b'smart', 'Hosts for inventory generated using the host_filter property.')]),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SmartInventoryMembership',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('host', models.ForeignKey(related_name='+', to='main.Host')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='smartinventorymembership',
|
||||||
|
name='inventory',
|
||||||
|
field=models.ForeignKey(related_name='+', to='main.Inventory'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='host',
|
||||||
|
name='smart_inventories',
|
||||||
|
field=models.ManyToManyField(related_name='_host_smart_inventories_+', through='main.SmartInventoryMembership', to='main.Inventory'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='smartinventorymembership',
|
||||||
|
unique_together=set([('host', 'inventory')]),
|
||||||
),
|
),
|
||||||
|
|
||||||
# Facts
|
# Facts
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ from awx.main.models.notifications import (
|
|||||||
)
|
)
|
||||||
from awx.main.utils import _inventory_updates
|
from awx.main.utils import _inventory_updates
|
||||||
|
|
||||||
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'CustomInventoryScript']
|
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate',
|
||||||
|
'CustomInventoryScript', 'SmartInventoryMembership']
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.inventory')
|
logger = logging.getLogger('awx.main.models.inventory')
|
||||||
|
|
||||||
@@ -346,6 +347,19 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
return self.groups.exclude(parents__pk__in=group_pks).distinct()
|
return self.groups.exclude(parents__pk__in=group_pks).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class SmartInventoryMembership(BaseModel):
|
||||||
|
'''
|
||||||
|
A lookup table for Host membership in Smart Inventory
|
||||||
|
'''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = 'main'
|
||||||
|
unique_together = (('host', 'inventory'),)
|
||||||
|
|
||||||
|
inventory = models.ForeignKey('Inventory', related_name='+', on_delete=models.CASCADE)
|
||||||
|
host = models.ForeignKey('Host', related_name='+', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
class Host(CommonModelNameNotUnique):
|
class Host(CommonModelNameNotUnique):
|
||||||
'''
|
'''
|
||||||
A managed node
|
A managed node
|
||||||
@@ -361,6 +375,11 @@ class Host(CommonModelNameNotUnique):
|
|||||||
related_name='hosts',
|
related_name='hosts',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
|
smart_inventories = models.ManyToManyField(
|
||||||
|
'Inventory',
|
||||||
|
related_name='+',
|
||||||
|
through='SmartInventoryMembership',
|
||||||
|
)
|
||||||
enabled = models.BooleanField(
|
enabled = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
help_text=_('Is this host online and available for running jobs?'),
|
help_text=_('Is this host online and available for running jobs?'),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from celery.signals import celeryd_init, worker_process_init
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction, DatabaseError
|
from django.db import transaction, DatabaseError, IntegrityError
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
@@ -62,8 +62,8 @@ from awx.main.utils.handlers import configure_external_logger
|
|||||||
from awx.main.consumers import emit_channel_notification
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
__all__ = ['RunJob', 'RunSystemJob', 'RunProjectUpdate', 'RunInventoryUpdate',
|
__all__ = ['RunJob', 'RunSystemJob', 'RunProjectUpdate', 'RunInventoryUpdate',
|
||||||
'RunAdHocCommand', 'handle_work_error',
|
'RunAdHocCommand', 'handle_work_error', 'handle_work_success',
|
||||||
'handle_work_success', 'update_inventory_computed_fields',
|
'update_inventory_computed_fields', 'update_host_smart_inventory_memberships',
|
||||||
'send_notifications', 'run_administrative_checks', 'purge_old_stdout_files']
|
'send_notifications', 'run_administrative_checks', 'purge_old_stdout_files']
|
||||||
|
|
||||||
HIDDEN_PASSWORD = '**********'
|
HIDDEN_PASSWORD = '**********'
|
||||||
@@ -321,6 +321,22 @@ def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
|
|||||||
i.update_computed_fields(update_hosts=should_update_hosts)
|
i.update_computed_fields(update_hosts=should_update_hosts)
|
||||||
|
|
||||||
|
|
||||||
|
@task(queue='tower')
|
||||||
|
def update_host_smart_inventory_memberships():
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
smart_inventories = Inventory.objects.filter(kind='smart', host_filter__isnull=False)
|
||||||
|
SmartInventoryMembership.objects.all().delete()
|
||||||
|
memberships = []
|
||||||
|
for smart_inventory in smart_inventories:
|
||||||
|
memberships.extend([SmartInventoryMembership(inventory_id=smart_inventory.id, host_id=host_id[0])
|
||||||
|
for host_id in smart_inventory.hosts.values_list('id')])
|
||||||
|
SmartInventoryMembership.objects.bulk_create(memberships)
|
||||||
|
except IntegrityError as e:
|
||||||
|
logger.error("Update Host Smart Inventory Memberships failed due to an exception: " + str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class BaseTask(Task):
|
class BaseTask(Task):
|
||||||
name = None
|
name = None
|
||||||
model = None
|
model = None
|
||||||
|
|||||||
@@ -568,6 +568,9 @@ AWX_TASK_ENV = {}
|
|||||||
# Flag to enable/disable updating hosts M2M when saving job events.
|
# Flag to enable/disable updating hosts M2M when saving job events.
|
||||||
CAPTURE_JOB_EVENT_HOSTS = False
|
CAPTURE_JOB_EVENT_HOSTS = False
|
||||||
|
|
||||||
|
# Rebuild Host Smart Inventory memberships.
|
||||||
|
AWX_REBUILD_SMART_MEMBERSHIP = False
|
||||||
|
|
||||||
# Enable bubblewrap support for running jobs (playbook runs only).
|
# Enable bubblewrap support for running jobs (playbook runs only).
|
||||||
# Note: This setting may be overridden by database settings.
|
# Note: This setting may be overridden by database settings.
|
||||||
AWX_PROOT_ENABLED = True
|
AWX_PROOT_ENABLED = True
|
||||||
|
|||||||
@@ -23,10 +23,16 @@ in our _Smart Search_.
|
|||||||
* The `Inventory` model has a new field called `kind`. The default of this field will be blank
|
* The `Inventory` model has a new field called `kind`. The default of this field will be blank
|
||||||
for normal inventories and set to `smart` for smart inventories.
|
for normal inventories and set to `smart` for smart inventories.
|
||||||
|
|
||||||
* `Inventory` model as a new field called `host_filter`. The default of this field will be blank
|
* `Inventory` model has a new field called `host_filter`. The default of this field will be blank
|
||||||
for normal inventories. When `host_filter` is set AND the inventory `kind` is set to `smart`
|
for normal inventories. When `host_filter` is set AND the inventory `kind` is set to `smart`
|
||||||
is the combination that makes a _Smart Inventory_.
|
is the combination that makes a _Smart Inventory_.
|
||||||
|
|
||||||
|
* `Host` model has a new field called `smart_inventories`. This field uses the `SmartInventoryMemberships`
|
||||||
|
lookup table to provide a set of all of the _Smart Inventory_ a host is a part of. The memberships
|
||||||
|
or generated by the `update_host_smart_inventory_memberships` task. This task is called when the view for
|
||||||
|
`/api/v2/hosts/:id/smart_inventories` is materialized. NOTE: This task is only run if the
|
||||||
|
`AWX_REBUILD_SMART_MEMBERSHIP` is set to True. It defaults to False.
|
||||||
|
|
||||||
### Smart Filter (host_filter)
|
### Smart Filter (host_filter)
|
||||||
The `SmartFilter` class handles our translation of the smart search string. We store the
|
The `SmartFilter` class handles our translation of the smart search string. We store the
|
||||||
filter value in the `host_filter` field for an inventory. This value should be expressed
|
filter value in the `host_filter` field for an inventory. This value should be expressed
|
||||||
|
|||||||
Reference in New Issue
Block a user