From bb61745a1a13e59ef0d95b665284ad169a9ae2cc Mon Sep 17 00:00:00 2001 From: David Wilson Date: Sun, 13 May 2018 03:18:22 +0100 Subject: [PATCH] issue #217: pass through non-custom module utils to regular importer. This may come back to bite later, but in the meantime it avoids shipping up to 12KiB of junk metadata for every single task invocation. For detachment (aka. async), we must ensure the target has two types of preloads completed (modules and module_utils files) before detaching. --- ansible_mitogen/module_finder.py | 66 +++++++++++++++++++++++--------- ansible_mitogen/planner.py | 21 ++++------ ansible_mitogen/services.py | 15 ++++++-- ansible_mitogen/target.py | 2 +- 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/ansible_mitogen/module_finder.py b/ansible_mitogen/module_finder.py index 44c06a3c..28522dd9 100644 --- a/ansible_mitogen/module_finder.py +++ b/ansible_mitogen/module_finder.py @@ -1,12 +1,37 @@ +# Copyright 2017, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import import collections import imp import os -import sys import mitogen.master -import ansible.module_utils PREFIX = 'ansible.module_utils.' @@ -16,6 +41,9 @@ Module = collections.namedtuple('Module', 'name path kind parent') def get_fullname(module): + """ + Reconstruct a Module's canonical path by recursing through its parents. + """ bits = [str(module.name)] while module.parent: bits.append(str(module.parent.name)) @@ -24,6 +52,9 @@ def get_fullname(module): def get_code(module): + """ + Compile and return a Module's code object. + """ fp = open(module.path) try: return compile(fp.read(), str(module.name), 'exec') @@ -32,12 +63,23 @@ def get_code(module): def is_pkg(module): + """ + Return :data:`True` if a Module represents a package. + """ return module.kind == imp.PKG_DIRECTORY def find(name, path=(), parent=None): """ - (Name, search path) -> Module instance or None. + Return a Module instance describing the first matching module found on the + given search path. + + :param str name: + Module name. + :param str path: + Search path. + :param Module parent: + If given, make the found module a child of this module. """ head, _, tail = name.partition('.') try: @@ -71,12 +113,7 @@ def scan_fromlist(code): def scan(module_name, module_path, search_path): - module = Module( - name=module_name, - path=module_path, - kind=imp.PY_SOURCE, - parent=None, - ) + module = Module(module_name, module_path, imp.PY_SOURCE, None) stack = [module] seen = set() @@ -90,19 +127,12 @@ def scan(module_name, module_path, search_path): if imported is None or imported in seen: continue - if imported in seen: - continue - seen.add(imported) stack.append(imported) parent = imported.parent while parent: - module = Module( - name=get_fullname(parent), - path=parent.path, - kind=parent.kind, - parent=None, - ) + fullname = get_fullname(parent) + module = Module(fullname, parent.path, parent.kind, None) if module not in seen: seen.add(module) stack.append(module) diff --git a/ansible_mitogen/planner.py b/ansible_mitogen/planner.py index 0e4499e9..e1f37fcd 100644 --- a/ansible_mitogen/planner.py +++ b/ansible_mitogen/planner.py @@ -292,38 +292,33 @@ class NewStylePlanner(ScriptPlanner): def detect(self, invocation): return 'from ansible.module_utils.' in invocation.module_source - def get_module_utils_path(self, invocation): - paths = [ + def get_search_path(self, invocation): + return tuple( path for path in module_utils_loader._get_paths(subdirs=False) if os.path.isdir(path) - ] - paths.append(module_common._MODULE_UTILS_PATH) - return tuple(paths) + ) def get_module_utils(self, invocation): invocation.connection._connect() - module_utils = mitogen.service.call( + return mitogen.service.call( context=invocation.connection.parent, handle=ansible_mitogen.services.ModuleDepService.handle, method='scan', kwargs={ 'module_name': 'ansible_module_%s' % (invocation.module_name,), 'module_path': invocation.module_path, - 'search_path': self.get_module_utils_path(invocation), + 'search_path': self.get_search_path(invocation), + 'builtin_path': module_common._MODULE_UTILS_PATH, } ) - modutils_dir = os.path.dirname(ansible.module_utils.__file__) - has_custom = not all(path.startswith(modutils_dir) - for fullname, path, is_pkg in module_utils) - return module_utils, has_custom def plan(self, invocation): - module_utils, has_custom = self.get_module_utils(invocation) + module_utils = self.get_module_utils(invocation) return super(NewStylePlanner, self).plan( invocation, module_utils=module_utils, - should_fork=(self.get_should_fork(invocation) or has_custom), + should_fork=(self.get_should_fork(invocation) or bool(module_utils)), ) diff --git a/ansible_mitogen/services.py b/ansible_mitogen/services.py index e7d6b2d7..0f5be35d 100644 --- a/ansible_mitogen/services.py +++ b/ansible_mitogen/services.py @@ -622,20 +622,27 @@ class ModuleDepService(mitogen.service.Service): 'module_name': basestring, 'module_path': basestring, 'search_path': tuple, + 'builtin_path': basestring, }) - def scan(self, module_name, module_path, search_path): + def scan(self, module_name, module_path, search_path, builtin_path): if (module_name, search_path) not in self._cache: resolved = ansible_mitogen.module_finder.scan( module_name=module_name, module_path=module_path, - search_path=search_path, + search_path=tuple(search_path) + (builtin_path,), ) - self._cache[module_name, search_path] = resolved + builtin_path = os.path.abspath(builtin_path) + filtered = [ + (fullname, path, is_pkg) + for fullname, path, is_pkg in resolved + if not os.path.abspath(path).startswith(builtin_path) + ] + self._cache[module_name, search_path] = filtered # Grant FileService access to paths in here to avoid another 2 IPCs # from WorkerProcess. self._file_service.register(path=module_path) - for fullname, path, is_pkg in resolved: + for fullname, path, is_pkg in filtered: self._file_service.register(path=path) return self._cache[module_name, search_path] diff --git a/ansible_mitogen/target.py b/ansible_mitogen/target.py index 5dc1dfa7..05deacf0 100644 --- a/ansible_mitogen/target.py +++ b/ansible_mitogen/target.py @@ -518,7 +518,7 @@ def write_path(path, s, owner=None, group=None, mode=None, prefix='.ansible_mitogen_transfer-', dir=os.path.dirname(path)) fp = os.fdopen(fd, 'wb', mitogen.core.CHUNK_SIZE) - LOG.debug('write_path(path=%r) tempory file: %s', path, tmp_path) + LOG.debug('write_path(path=%r) temporary file: %s', path, tmp_path) try: try: