Add smart_inventories endpoint to Host

This commit is contained in:
Wayne Witzel III
2017-05-26 10:10:31 -04:00
parent 5232b70897
commit e28cd97ffb
8 changed files with 92 additions and 7 deletions

View File

@@ -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}),

View File

@@ -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'),

View File

@@ -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

View File

@@ -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

View File

@@ -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?'),

View File

@@ -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

View File

@@ -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

View File

@@ -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