Make hostvars more dynamic again to improve performance with large inventories

Fixes #12477
This commit is contained in:
James Cammarata 2015-09-24 13:53:44 -04:00
parent 82b33c381f
commit 12df9f2e31
3 changed files with 45 additions and 15 deletions

View file

@ -66,6 +66,7 @@ class Group:
self.__init__() self.__init__()
self.name = data.get('name') self.name = data.get('name')
self.vars = data.get('vars', dict()) self.vars = data.get('vars', dict())
self.depth = data.get('depth', 0)
parent_groups = data.get('parent_groups', []) parent_groups = data.get('parent_groups', [])
for parent_data in parent_groups: for parent_data in parent_groups:

View file

@ -43,7 +43,8 @@ from ansible.utils.vars import combine_vars
from ansible.vars.hostvars import HostVars from ansible.vars.hostvars import HostVars
from ansible.vars.unsafe_proxy import UnsafeProxy from ansible.vars.unsafe_proxy import UnsafeProxy
CACHED_VARS = dict() VARIABLE_CACHE = dict()
HOSTVARS_CACHE = dict()
try: try:
from __main__ import display from __main__ import display
@ -85,6 +86,28 @@ class VariableManager:
self._inventory = None self._inventory = None
self._omit_token = '__omit_place_holder__%s' % sha1(os.urandom(64)).hexdigest() self._omit_token = '__omit_place_holder__%s' % sha1(os.urandom(64)).hexdigest()
def __getstate__(self):
data = dict(
fact_cache = self._fact_cache.copy(),
np_fact_cache = self._nonpersistent_fact_cache.copy(),
vars_cache = self._vars_cache.copy(),
extra_vars = self._extra_vars.copy(),
host_vars_files = self._host_vars_files.copy(),
group_vars_files = self._group_vars_files.copy(),
omit_token = self._omit_token,
)
return data
def __setstate__(self, data):
self._fact_cache = data.get('fact_cache', defaultdict(dict))
self._nonpersistent_fact_cache = data.get('np_fact_cache', defaultdict(dict))
self._vars_cache = data.get('vars_cache', defaultdict(dict))
self._extra_vars = data.get('extra_vars', dict())
self._host_vars_files = data.get('host_vars_files', defaultdict(dict))
self._group_vars_files = data.get('group_vars_files', defaultdict(dict))
self._omit_token = data.get('omit_token', '__omit_place_holder__%s' % sha1(os.urandom(64)).hexdigest())
self._inventory = None
def _get_cache_entry(self, play=None, host=None, task=None): def _get_cache_entry(self, play=None, host=None, task=None):
play_id = "NONE" play_id = "NONE"
if play: if play:
@ -157,9 +180,9 @@ class VariableManager:
debug("in VariableManager get_vars()") debug("in VariableManager get_vars()")
cache_entry = self._get_cache_entry(play=play, host=host, task=task) cache_entry = self._get_cache_entry(play=play, host=host, task=task)
if cache_entry in CACHED_VARS and use_cache: if cache_entry in VARIABLE_CACHE and use_cache:
debug("vars are cached, returning them now") debug("vars are cached, returning them now")
return CACHED_VARS[cache_entry] return VARIABLE_CACHE[cache_entry]
all_vars = defaultdict(dict) all_vars = defaultdict(dict)
@ -289,7 +312,12 @@ class VariableManager:
all_vars['groups'][group_name] = [h.name for h in group.get_hosts()] all_vars['groups'][group_name] = [h.name for h in group.get_hosts()]
if include_hostvars: if include_hostvars:
hostvars = HostVars(vars_manager=self, play=play, inventory=self._inventory, loader=loader) hostvars_cache_entry = self._get_cache_entry(play=play)
if hostvars_cache_entry in HOSTVARS_CACHE:
hostvars = HOSTVARS_CACHE[hostvars_cache_entry]
else:
hostvars = HostVars(play=play, inventory=self._inventory, loader=loader, variable_manager=self)
HOSTVARS_CACHE[hostvars_cache_entry] = hostvars
all_vars['hostvars'] = hostvars all_vars['hostvars'] = hostvars
if task: if task:
@ -355,7 +383,7 @@ class VariableManager:
if 'hostvars' in all_vars and host: if 'hostvars' in all_vars and host:
all_vars['vars'] = all_vars['hostvars'][host.get_name()] all_vars['vars'] = all_vars['hostvars'][host.get_name()]
#CACHED_VARS[cache_entry] = all_vars #VARIABLE_CACHE[cache_entry] = all_vars
debug("done with get_vars()") debug("done with get_vars()")
return all_vars return all_vars

View file

@ -34,9 +34,11 @@ __all__ = ['HostVars']
class HostVars(collections.Mapping): class HostVars(collections.Mapping):
''' A special view of vars_cache that adds values from the inventory when needed. ''' ''' A special view of vars_cache that adds values from the inventory when needed. '''
def __init__(self, vars_manager, play, inventory, loader): def __init__(self, play, inventory, variable_manager, loader):
self._lookup = {} self._lookup = dict()
self._loader = loader self._loader = loader
self._play = play
self._variable_manager = variable_manager
hosts = inventory.get_hosts(ignore_limits_and_restrictions=True) hosts = inventory.get_hosts(ignore_limits_and_restrictions=True)
@ -49,9 +51,6 @@ class HostVars(collections.Mapping):
has_localhost = True has_localhost = True
break break
# we don't use the method in inventory to create the implicit host,
# because it also adds it to the 'ungrouped' group, and we want to
# avoid any side-effects
if not has_localhost: if not has_localhost:
new_host = Host(name='localhost') new_host = Host(name='localhost')
new_host.set_variable("ansible_python_interpreter", sys.executable) new_host.set_variable("ansible_python_interpreter", sys.executable)
@ -60,14 +59,15 @@ class HostVars(collections.Mapping):
hosts.append(new_host) hosts.append(new_host)
for host in hosts: for host in hosts:
self._lookup[host.name] = vars_manager.get_vars(loader=loader, play=play, host=host, include_hostvars=False) self._lookup[host.name] = host
def __getitem__(self, host_name): def __getitem__(self, host_name):
if host_name not in self._lookup: if host_name not in self._lookup:
return j2undefined return j2undefined
data = self._lookup.get(host_name) host = self._lookup.get(host_name)
data = self._variable_manager.get_vars(loader=self._loader, host=host, play=self._play, include_hostvars=False)
templar = Templar(variables=data, loader=self._loader) templar = Templar(variables=data, loader=self._loader)
return templar.template(data, fail_on_undefined=False) return templar.template(data, fail_on_undefined=False)
@ -84,9 +84,10 @@ class HostVars(collections.Mapping):
raise NotImplementedError('HostVars does not support len. hosts entries are discovered dynamically as needed') raise NotImplementedError('HostVars does not support len. hosts entries are discovered dynamically as needed')
def __getstate__(self): def __getstate__(self):
data = self._lookup.copy() return dict(loader=self._loader, lookup=self._lookup, play=self._play, var_manager=self._variable_manager)
return dict(loader=self._loader, data=data)
def __setstate__(self, data): def __setstate__(self, data):
self._lookup = data.get('data') self._play = data.get('play')
self._loader = data.get('loader') self._loader = data.get('loader')
self._lookup = data.get('lookup')
self._variable_manager = data.get('var_manager')