From b9b0b29d973f6c9dfb172f7c026db8b1d3ee3191 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 10 May 2017 15:26:51 -0400 Subject: [PATCH 1/4] associate insights machine id w/ host on fact scan * Add inisghts fact scan module * Update fact scan playbook to call new insight fact scan module * JT run w/ store_facts=True will save scanned facts. We "skim" the machine_id fact from Insights fact scans and associate it with the host that the fact scan came from. --- .../commands/run_fact_cache_receiver.py | 6 ++ awx/main/migrations/0038_v320_release.py | 11 +++ awx/main/models/inventory.py | 9 ++- awx/playbooks/scan_facts.yml | 3 + awx/plugins/library/scan_insights.py | 68 +++++++++++++++++++ 5 files changed, 96 insertions(+), 1 deletion(-) create mode 100755 awx/plugins/library/scan_insights.py diff --git a/awx/main/management/commands/run_fact_cache_receiver.py b/awx/main/management/commands/run_fact_cache_receiver.py index 1d03757eb2..4bc97eea05 100644 --- a/awx/main/management/commands/run_fact_cache_receiver.py +++ b/awx/main/management/commands/run_fact_cache_receiver.py @@ -120,6 +120,12 @@ class FactBrokerWorker(ConsumerMixin): ret = self._do_fact_scan_create_update(host_obj, module_name, facts, self.timestamp) if job.store_facts is True: + if module_name == 'insights': + try: + host_obj.insights_machine_id = facts['machine_id'] + host_obj.save() + except StandardError: + logger.warn('Failed to find insights machine id in insights fact scan.') self._do_gather_facts_update(host_obj, module_name, facts, self.timestamp) message.ack() diff --git a/awx/main/migrations/0038_v320_release.py b/awx/main/migrations/0038_v320_release.py index 3ffcd98c81..cd8aa60024 100644 --- a/awx/main/migrations/0038_v320_release.py +++ b/awx/main/migrations/0038_v320_release.py @@ -236,4 +236,15 @@ class Migration(migrations.Migration): migrations.DeleteModel( name='Permission', ), + + # Insights + migrations.AddField( + model_name='host', + name='insights_machine_id', + field=models.TextField(default=None, help_text='Red Hat Insights host unique identifier.', null=True, db_index=True, blank=True), + ), + migrations.AlterUniqueTogether( + name='host', + unique_together=set([('insights_machine_id', 'inventory'), ('name', 'inventory')]), + ), ] diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 9659a35ba0..a63fa3859e 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -350,7 +350,7 @@ class Host(CommonModelNameNotUnique): class Meta: app_label = 'main' - unique_together = (("name", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration. + unique_together = (("name", "inventory"), ("insights_machine_id", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration. ordering = ('name',) inventory = models.ForeignKey( @@ -411,6 +411,13 @@ class Host(CommonModelNameNotUnique): default={}, help_text=_('Arbitrary JSON structure of most recent ansible_facts, per-host.'), ) + insights_machine_id = models.TextField( + blank=True, + default=None, + null=True, + db_index=True, + help_text=_('Red Hat Insights host unique identifier.'), + ) objects = HostManager() diff --git a/awx/playbooks/scan_facts.yml b/awx/playbooks/scan_facts.yml index d24d07d6fa..56b649c3c8 100644 --- a/awx/playbooks/scan_facts.yml +++ b/awx/playbooks/scan_facts.yml @@ -17,6 +17,9 @@ get_checksum: '{{ scan_use_checksum }}' recursive: '{{ scan_use_recursive }}' when: scan_file_paths is defined and ansible_os_family != "Windows" + - name: "Scan Insights for Machine ID (Unix/Linux)" + scan_insights: + when: ansible_os_family != "Windows" - name: "Scan packages (Windows)" win_scan_packages: diff --git a/awx/plugins/library/scan_insights.py b/awx/plugins/library/scan_insights.py new file mode 100755 index 0000000000..5b6829dcb2 --- /dev/null +++ b/awx/plugins/library/scan_insights.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +from ansible.module_utils.basic import * # noqa +import uuid + +DOCUMENTATION = ''' +--- +module: scan_insights +short_description: Return insights UUID as fact data +description: + - Inspects the /etc/redhat-access-insights/machine-id file for insights uuid and returns the found UUID as fact data +version_added: "2.3" +options: +requirements: [ ] +author: Chris Meyers +''' + +EXAMPLES = ''' +# Example fact output: +# host | success >> { +# "ansible_facts": { +# "insights": { +# "machine_id": "4da7d1f8-14f3-4cdc-acd5-a3465a41f25d" +# }, ... } +''' + + +INSIGHTS_MACHINE_ID_FILE='/etc/redhat-access-insights/machine-id' + + +def get_machine_uuid(filname): + machine_uuid = None + try: + f = open(INSIGHTS_MACHINE_ID_FILE, "r") + except IOError: + return None + else: + try: + data = f.readline() + machine_uuid = str(uuid.UUID(data)) + except (IOError, ValueError): + pass + finally: + f.close() + return machine_uuid + + +def main(): + module = AnsibleModule( + argument_spec = dict() + ) + + machine_uuid = get_machine_uuid(INSIGHTS_MACHINE_ID_FILE) + + if machine_uuid is not None: + results = { + 'ansible_facts': { + 'insights': { + 'machine_id': machine_uuid + } + } + } + else: + results = dict(skipped=True, msg="Insights machine id not found") + module.exit_json(**results) + + +main() From 3cdc332497b8ee32bd57b0f7dd8a71aad8caab3a Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 12 May 2017 10:47:49 -0400 Subject: [PATCH 2/4] rename insight tracking field; handle duplicate * rename from insights_machine_id to insights_system_id * gracefully handle duplicate insights machine id's --- .../commands/run_fact_cache_receiver.py | 16 +++++++++----- awx/main/migrations/0038_v320_release.py | 4 ++-- awx/main/models/inventory.py | 4 ++-- awx/plugins/library/scan_insights.py | 22 +++++++++---------- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/awx/main/management/commands/run_fact_cache_receiver.py b/awx/main/management/commands/run_fact_cache_receiver.py index 4bc97eea05..72f2fce881 100644 --- a/awx/main/management/commands/run_fact_cache_receiver.py +++ b/awx/main/management/commands/run_fact_cache_receiver.py @@ -12,6 +12,7 @@ from kombu.mixins import ConsumerMixin from django.core.management.base import NoArgsCommand from django.conf import settings from django.utils import timezone +from django.db import IntegrityError # AWX from awx.main.models.jobs import Job @@ -121,11 +122,16 @@ class FactBrokerWorker(ConsumerMixin): if job.store_facts is True: if module_name == 'insights': - try: - host_obj.insights_machine_id = facts['machine_id'] - host_obj.save() - except StandardError: - logger.warn('Failed to find insights machine id in insights fact scan.') + system_id = facts.get('system_id', None) + if system_id: + host_obj.insights_system_id = system_id + try: + host_obj.save() + except IntegrityError: + host_obj.insights_system_id = None + logger.warn('Inisghts system_id %s not assigned to host %s because it already exists.' % (system_id, host_obj.pk)) + else: + logger.warn('Failed to find insights system id in insights fact scan.') self._do_gather_facts_update(host_obj, module_name, facts, self.timestamp) message.ack() diff --git a/awx/main/migrations/0038_v320_release.py b/awx/main/migrations/0038_v320_release.py index cd8aa60024..d28a8cce0b 100644 --- a/awx/main/migrations/0038_v320_release.py +++ b/awx/main/migrations/0038_v320_release.py @@ -240,11 +240,11 @@ class Migration(migrations.Migration): # Insights migrations.AddField( model_name='host', - name='insights_machine_id', + name='insights_system_id', field=models.TextField(default=None, help_text='Red Hat Insights host unique identifier.', null=True, db_index=True, blank=True), ), migrations.AlterUniqueTogether( name='host', - unique_together=set([('insights_machine_id', 'inventory'), ('name', 'inventory')]), + unique_together=set([('insights_system_id', 'inventory'), ('name', 'inventory')]), ), ] diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index a63fa3859e..db4c13f313 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -350,7 +350,7 @@ class Host(CommonModelNameNotUnique): class Meta: app_label = 'main' - unique_together = (("name", "inventory"), ("insights_machine_id", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration. + unique_together = (("name", "inventory"), ("insights_system_id", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration. ordering = ('name',) inventory = models.ForeignKey( @@ -411,7 +411,7 @@ class Host(CommonModelNameNotUnique): default={}, help_text=_('Arbitrary JSON structure of most recent ansible_facts, per-host.'), ) - insights_machine_id = models.TextField( + insights_system_id = models.TextField( blank=True, default=None, null=True, diff --git a/awx/plugins/library/scan_insights.py b/awx/plugins/library/scan_insights.py index 5b6829dcb2..a697cd7d89 100755 --- a/awx/plugins/library/scan_insights.py +++ b/awx/plugins/library/scan_insights.py @@ -20,29 +20,29 @@ EXAMPLES = ''' # host | success >> { # "ansible_facts": { # "insights": { -# "machine_id": "4da7d1f8-14f3-4cdc-acd5-a3465a41f25d" +# "system_id": "4da7d1f8-14f3-4cdc-acd5-a3465a41f25d" # }, ... } ''' -INSIGHTS_MACHINE_ID_FILE='/etc/redhat-access-insights/machine-id' +INSIGHTS_SYSTEM_ID_FILE='/etc/redhat-access-insights/machine-id' -def get_machine_uuid(filname): - machine_uuid = None +def get_system_uuid(filname): + system_uuid = None try: - f = open(INSIGHTS_MACHINE_ID_FILE, "r") + f = open(INSIGHTS_SYSTEM_ID_FILE, "r") except IOError: return None else: try: data = f.readline() - machine_uuid = str(uuid.UUID(data)) + system_uuid = str(uuid.UUID(data)) except (IOError, ValueError): pass finally: f.close() - return machine_uuid + return system_uuid def main(): @@ -50,18 +50,18 @@ def main(): argument_spec = dict() ) - machine_uuid = get_machine_uuid(INSIGHTS_MACHINE_ID_FILE) + system_uuid = get_system_uuid(INSIGHTS_SYSTEM_ID_FILE) - if machine_uuid is not None: + if system_uuid is not None: results = { 'ansible_facts': { 'insights': { - 'machine_id': machine_uuid + 'system_id': system_uuid } } } else: - results = dict(skipped=True, msg="Insights machine id not found") + results = dict(skipped=True, msg="Insights system id not found") module.exit_json(**results) From eb27233e5cece947acee07bdb68985dc802d4908 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 12 May 2017 12:00:30 -0400 Subject: [PATCH 3/4] clear insights system id if not found * Host previously found to have an insights system id that then later are found to not have an insights system id, have their system id cleared. --- .../commands/run_fact_cache_receiver.py | 15 ++++++--------- awx/plugins/library/scan_insights.py | 13 +++++-------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/awx/main/management/commands/run_fact_cache_receiver.py b/awx/main/management/commands/run_fact_cache_receiver.py index 72f2fce881..4d00f5c157 100644 --- a/awx/main/management/commands/run_fact_cache_receiver.py +++ b/awx/main/management/commands/run_fact_cache_receiver.py @@ -123,15 +123,12 @@ class FactBrokerWorker(ConsumerMixin): if job.store_facts is True: if module_name == 'insights': system_id = facts.get('system_id', None) - if system_id: - host_obj.insights_system_id = system_id - try: - host_obj.save() - except IntegrityError: - host_obj.insights_system_id = None - logger.warn('Inisghts system_id %s not assigned to host %s because it already exists.' % (system_id, host_obj.pk)) - else: - logger.warn('Failed to find insights system id in insights fact scan.') + host_obj.insights_system_id = system_id + try: + host_obj.save() + except IntegrityError: + host_obj.insights_system_id = None + logger.warn('Inisghts system_id %s not assigned to host %s because it already exists.' % (system_id, host_obj.pk)) self._do_gather_facts_update(host_obj, module_name, facts, self.timestamp) message.ack() diff --git a/awx/plugins/library/scan_insights.py b/awx/plugins/library/scan_insights.py index a697cd7d89..b7e625ca6d 100755 --- a/awx/plugins/library/scan_insights.py +++ b/awx/plugins/library/scan_insights.py @@ -52,16 +52,13 @@ def main(): system_uuid = get_system_uuid(INSIGHTS_SYSTEM_ID_FILE) - if system_uuid is not None: - results = { - 'ansible_facts': { - 'insights': { - 'system_id': system_uuid - } + results = { + 'ansible_facts': { + 'insights': { + 'system_id': system_uuid } } - else: - results = dict(skipped=True, msg="Insights system id not found") + } module.exit_json(**results) From ac534abe24a4bcd4c432a1c3d53a2fe3436c44d7 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 12 May 2017 12:09:20 -0400 Subject: [PATCH 4/4] expose insights_system_id via api * insights_system_id is read-only. Only modified via the fact gathering code path. --- awx/api/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 25fa56c8bf..92416e13d4 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1175,8 +1175,8 @@ class HostSerializer(BaseSerializerWithVariables): model = Host fields = ('*', 'inventory', 'enabled', 'instance_id', 'variables', 'has_active_failures', 'has_inventory_sources', 'last_job', - 'last_job_host_summary') - read_only_fields = ('last_job', 'last_job_host_summary') + 'last_job_host_summary', 'insights_system_id') + read_only_fields = ('last_job', 'last_job_host_summary', 'insights_system_id',) def build_relational_field(self, field_name, relation_info): field_class, field_kwargs = super(HostSerializer, self).build_relational_field(field_name, relation_info)