|
2 | 2 | # All Rights Reserved.
|
3 | 3 |
|
4 | 4 | # Python
|
| 5 | +import codecs |
5 | 6 | import datetime
|
6 | 7 | import logging
|
| 8 | +import os |
7 | 9 | import time
|
8 | 10 | import json
|
9 |
| -import base64 |
10 | 11 | from urlparse import urljoin
|
11 | 12 |
|
| 13 | +import six |
| 14 | + |
12 | 15 | # Django
|
13 | 16 | from django.conf import settings
|
14 | 17 | from django.db import models
|
15 | 18 | #from django.core.cache import cache
|
16 |
| -import memcache |
17 |
| -from dateutil import parser |
18 |
| -from dateutil.tz import tzutc |
19 | 19 | from django.utils.encoding import smart_str
|
| 20 | +from django.utils.timezone import now |
20 | 21 | from django.utils.translation import ugettext_lazy as _
|
21 | 22 | from django.core.exceptions import ValidationError, FieldDoesNotExist
|
22 | 23 |
|
@@ -738,86 +739,68 @@ def get_notification_templates(self):
|
738 | 739 | def get_notification_friendly_name(self):
|
739 | 740 | return "Job"
|
740 | 741 |
|
741 |
| - @property |
742 |
| - def memcached_fact_key(self): |
743 |
| - return '{}'.format(self.inventory.id) |
744 |
| - |
745 |
| - def memcached_fact_host_key(self, host_name): |
746 |
| - return '{}-{}'.format(self.inventory.id, base64.b64encode(host_name.encode('utf-8'))) |
747 |
| - |
748 |
| - def memcached_fact_modified_key(self, host_name): |
749 |
| - return '{}-{}-modified'.format(self.inventory.id, base64.b64encode(host_name.encode('utf-8'))) |
750 |
| - |
751 |
| - def _get_inventory_hosts(self, only=['name', 'ansible_facts', 'modified',]): |
752 |
| - return self.inventory.hosts.only(*only) |
753 |
| - |
754 |
| - def _get_memcache_connection(self): |
755 |
| - return memcache.Client([settings.CACHES['default']['LOCATION']], debug=0) |
756 |
| - |
757 |
| - def start_job_fact_cache(self): |
758 |
| - if not self.inventory: |
759 |
| - return |
760 |
| - |
761 |
| - cache = self._get_memcache_connection() |
762 |
| - |
763 |
| - host_names = [] |
764 |
| - |
765 |
| - for host in self._get_inventory_hosts(): |
766 |
| - host_key = self.memcached_fact_host_key(host.name) |
767 |
| - modified_key = self.memcached_fact_modified_key(host.name) |
768 |
| - |
769 |
| - if cache.get(modified_key) is None: |
770 |
| - if host.ansible_facts_modified: |
771 |
| - host_modified = host.ansible_facts_modified.replace(tzinfo=tzutc()).isoformat() |
772 |
| - else: |
773 |
| - host_modified = datetime.datetime.now(tzutc()).isoformat() |
774 |
| - cache.set(host_key, json.dumps(host.ansible_facts)) |
775 |
| - cache.set(modified_key, host_modified) |
776 |
| - |
777 |
| - host_names.append(host.name) |
778 |
| - |
779 |
| - cache.set(self.memcached_fact_key, host_names) |
780 |
| - |
781 |
| - def finish_job_fact_cache(self): |
| 742 | + def _get_inventory_hosts(self, only=['name', 'ansible_facts', 'ansible_facts_modified', 'modified',]): |
782 | 743 | if not self.inventory:
|
783 |
| - return |
784 |
| - |
785 |
| - cache = self._get_memcache_connection() |
| 744 | + return [] |
| 745 | + return self.inventory.hosts.only(*only) |
786 | 746 |
|
| 747 | + def start_job_fact_cache(self, destination, modification_times, timeout=None): |
| 748 | + destination = os.path.join(destination, 'facts') |
| 749 | + os.makedirs(destination, mode=0700) |
787 | 750 | hosts = self._get_inventory_hosts()
|
| 751 | + if timeout is None: |
| 752 | + timeout = settings.ANSIBLE_FACT_CACHE_TIMEOUT |
| 753 | + if timeout > 0: |
| 754 | + # exclude hosts with fact data older than `settings.ANSIBLE_FACT_CACHE_TIMEOUT seconds` |
| 755 | + timeout = now() - datetime.timedelta(seconds=timeout) |
| 756 | + hosts = hosts.filter(ansible_facts_modified__gte=timeout) |
788 | 757 | for host in hosts:
|
789 |
| - host_key = self.memcached_fact_host_key(host.name) |
790 |
| - modified_key = self.memcached_fact_modified_key(host.name) |
791 |
| - |
792 |
| - modified = cache.get(modified_key) |
793 |
| - if modified is None: |
794 |
| - cache.delete(host_key) |
| 758 | + filepath = os.sep.join(map(six.text_type, [destination, host.name])) |
| 759 | + if not os.path.realpath(filepath).startswith(destination): |
| 760 | + system_tracking_logger.error('facts for host {} could not be cached'.format(smart_str(host.name))) |
795 | 761 | continue
|
796 |
| - |
797 |
| - # Save facts if cache is newer than DB |
798 |
| - modified = parser.parse(modified, tzinfos=[tzutc()]) |
799 |
| - if not host.ansible_facts_modified or modified > host.ansible_facts_modified: |
800 |
| - ansible_facts = cache.get(host_key) |
801 |
| - try: |
802 |
| - ansible_facts = json.loads(ansible_facts) |
803 |
| - except Exception: |
804 |
| - ansible_facts = None |
805 |
| - |
806 |
| - if ansible_facts is None: |
807 |
| - cache.delete(host_key) |
808 |
| - continue |
809 |
| - host.ansible_facts = ansible_facts |
810 |
| - host.ansible_facts_modified = modified |
811 |
| - if 'insights' in ansible_facts and 'system_id' in ansible_facts['insights']: |
812 |
| - host.insights_system_id = ansible_facts['insights']['system_id'] |
813 |
| - host.save() |
| 762 | + with codecs.open(filepath, 'w', encoding='utf-8') as f: |
| 763 | + os.chmod(f.name, 0600) |
| 764 | + json.dump(host.ansible_facts, f) |
| 765 | + # make note of the time we wrote the file so we can check if it changed later |
| 766 | + modification_times[filepath] = os.path.getmtime(filepath) |
| 767 | + |
| 768 | + def finish_job_fact_cache(self, destination, modification_times): |
| 769 | + destination = os.path.join(destination, 'facts') |
| 770 | + for host in self._get_inventory_hosts(): |
| 771 | + filepath = os.sep.join(map(six.text_type, [destination, host.name])) |
| 772 | + if not os.path.realpath(filepath).startswith(destination): |
| 773 | + system_tracking_logger.error('facts for host {} could not be cached'.format(smart_str(host.name))) |
| 774 | + continue |
| 775 | + if os.path.exists(filepath): |
| 776 | + # If the file changed since we wrote it pre-playbook run... |
| 777 | + modified = os.path.getmtime(filepath) |
| 778 | + if modified > modification_times.get(filepath, 0): |
| 779 | + with codecs.open(filepath, 'r', encoding='utf-8') as f: |
| 780 | + try: |
| 781 | + ansible_facts = json.load(f) |
| 782 | + except ValueError: |
| 783 | + continue |
| 784 | + host.ansible_facts = ansible_facts |
| 785 | + host.ansible_facts_modified = now() |
| 786 | + if 'insights' in ansible_facts and 'system_id' in ansible_facts['insights']: |
| 787 | + host.insights_system_id = ansible_facts['insights']['system_id'] |
| 788 | + host.save() |
| 789 | + system_tracking_logger.info( |
| 790 | + 'New fact for inventory {} host {}'.format( |
| 791 | + smart_str(host.inventory.name), smart_str(host.name)), |
| 792 | + extra=dict(inventory_id=host.inventory.id, host_name=host.name, |
| 793 | + ansible_facts=host.ansible_facts, |
| 794 | + ansible_facts_modified=host.ansible_facts_modified.isoformat(), |
| 795 | + job_id=self.id)) |
| 796 | + else: |
| 797 | + # if the file goes missing, ansible removed it (likely via clear_facts) |
| 798 | + host.ansible_facts = {} |
| 799 | + host.ansible_facts_modified = now() |
814 | 800 | system_tracking_logger.info(
|
815 |
| - 'New fact for inventory {} host {}'.format( |
816 |
| - smart_str(host.inventory.name), smart_str(host.name)), |
817 |
| - extra=dict(inventory_id=host.inventory.id, host_name=host.name, |
818 |
| - ansible_facts=host.ansible_facts, |
819 |
| - ansible_facts_modified=host.ansible_facts_modified.isoformat(), |
820 |
| - job_id=self.id)) |
| 801 | + 'Facts cleared for inventory {} host {}'.format( |
| 802 | + smart_str(host.inventory.name), smart_str(host.name))) |
| 803 | + host.save() |
821 | 804 |
|
822 | 805 |
|
823 | 806 | # Add on aliases for the non-related-model fields
|
|
0 commit comments