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.
pull/255/head
David Wilson 6 years ago
parent 30034877a5
commit bb61745a1a

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

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

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

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

Loading…
Cancel
Save