issue #217: ansible: working, if extremely inefficient implementation
This commit is contained in:
parent
81b62d9a1a
commit
30034877a5
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
import collections
|
||||||
import imp
|
import imp
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -11,33 +12,27 @@ import ansible.module_utils
|
||||||
PREFIX = 'ansible.module_utils.'
|
PREFIX = 'ansible.module_utils.'
|
||||||
|
|
||||||
|
|
||||||
class Module(object):
|
Module = collections.namedtuple('Module', 'name path kind parent')
|
||||||
def __init__(self, name, path, kind=imp.PY_SOURCE, parent=None):
|
|
||||||
self.name = name
|
|
||||||
self.path = path
|
|
||||||
self.kind = kind
|
|
||||||
self.is_pkg = kind == imp.PKG_DIRECTORY
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.path)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def get_fullname(module):
|
||||||
return self.path == other.path
|
bits = [str(module.name)]
|
||||||
|
while module.parent:
|
||||||
|
bits.append(str(module.parent.name))
|
||||||
|
module = module.parent
|
||||||
|
return '.'.join(reversed(bits))
|
||||||
|
|
||||||
def fullname(self):
|
|
||||||
bits = [str(self.name)]
|
|
||||||
while self.parent:
|
|
||||||
bits.append(str(self.parent.name))
|
|
||||||
self = self.parent
|
|
||||||
return '.'.join(reversed(bits))
|
|
||||||
|
|
||||||
def code(self):
|
def get_code(module):
|
||||||
fp = open(self.path)
|
fp = open(module.path)
|
||||||
try:
|
try:
|
||||||
return compile(fp.read(), str(self.name), 'exec')
|
return compile(fp.read(), str(module.name), 'exec')
|
||||||
finally:
|
finally:
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
|
|
||||||
|
def is_pkg(module):
|
||||||
|
return module.kind == imp.PKG_DIRECTORY
|
||||||
|
|
||||||
|
|
||||||
def find(name, path=(), parent=None):
|
def find(name, path=(), parent=None):
|
||||||
|
@ -76,13 +71,18 @@ def scan_fromlist(code):
|
||||||
|
|
||||||
|
|
||||||
def scan(module_name, module_path, search_path):
|
def scan(module_name, module_path, search_path):
|
||||||
module = Module(module_name, module_path)
|
module = Module(
|
||||||
|
name=module_name,
|
||||||
|
path=module_path,
|
||||||
|
kind=imp.PY_SOURCE,
|
||||||
|
parent=None,
|
||||||
|
)
|
||||||
stack = [module]
|
stack = [module]
|
||||||
seen = set()
|
seen = set()
|
||||||
|
|
||||||
while stack:
|
while stack:
|
||||||
module = stack.pop(0)
|
module = stack.pop(0)
|
||||||
for level, fromname in scan_fromlist(module.code()):
|
for level, fromname in scan_fromlist(get_code(module)):
|
||||||
if not fromname.startswith(PREFIX):
|
if not fromname.startswith(PREFIX):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -90,17 +90,25 @@ def scan(module_name, module_path, search_path):
|
||||||
if imported is None or imported in seen:
|
if imported is None or imported in seen:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if imported in seen:
|
||||||
|
continue
|
||||||
|
|
||||||
seen.add(imported)
|
seen.add(imported)
|
||||||
|
stack.append(imported)
|
||||||
parent = imported.parent
|
parent = imported.parent
|
||||||
while parent:
|
while parent:
|
||||||
module = Module(name=parent.fullname(), path=parent.path,
|
module = Module(
|
||||||
kind=parent.kind)
|
name=get_fullname(parent),
|
||||||
|
path=parent.path,
|
||||||
|
kind=parent.kind,
|
||||||
|
parent=None,
|
||||||
|
)
|
||||||
if module not in seen:
|
if module not in seen:
|
||||||
seen.add(module)
|
seen.add(module)
|
||||||
stack.append(module)
|
stack.append(module)
|
||||||
parent = parent.parent
|
parent = parent.parent
|
||||||
|
|
||||||
return sorted(
|
return sorted(
|
||||||
(PREFIX + module.fullname(), module.path, module.is_pkg)
|
(PREFIX + get_fullname(module), module.path, is_pkg(module))
|
||||||
for module in seen
|
for module in seen
|
||||||
)
|
)
|
||||||
|
|
|
@ -180,7 +180,7 @@ class BinaryPlanner(Planner):
|
||||||
def detect(self, invocation):
|
def detect(self, invocation):
|
||||||
return module_common._is_binary(invocation.module_source)
|
return module_common._is_binary(invocation.module_source)
|
||||||
|
|
||||||
def plan(self, invocation, **kwargs):
|
def _grant_file_service_access(self, invocation):
|
||||||
invocation.connection._connect()
|
invocation.connection._connect()
|
||||||
mitogen.service.call(
|
mitogen.service.call(
|
||||||
context=invocation.connection.parent,
|
context=invocation.connection.parent,
|
||||||
|
@ -190,6 +190,9 @@ class BinaryPlanner(Planner):
|
||||||
'path': invocation.module_path
|
'path': invocation.module_path
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def plan(self, invocation, **kwargs):
|
||||||
|
self._grant_file_service_access(invocation)
|
||||||
return super(BinaryPlanner, self).plan(
|
return super(BinaryPlanner, self).plan(
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
runner_name=self.runner_name,
|
runner_name=self.runner_name,
|
||||||
|
@ -270,6 +273,12 @@ class NewStylePlanner(ScriptPlanner):
|
||||||
def _get_interpreter(self, invocation):
|
def _get_interpreter(self, invocation):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
def _grant_file_service_access(self, invocation):
|
||||||
|
"""
|
||||||
|
Stub out BinaryPlanner's method since ModuleDepService makes internal
|
||||||
|
calls to grant file access, avoiding 2 IPCs per task invocation.
|
||||||
|
"""
|
||||||
|
|
||||||
def get_should_fork(self, invocation):
|
def get_should_fork(self, invocation):
|
||||||
"""
|
"""
|
||||||
In addition to asynchronous tasks, new-style modules should be forked
|
In addition to asynchronous tasks, new-style modules should be forked
|
||||||
|
@ -293,6 +302,7 @@ class NewStylePlanner(ScriptPlanner):
|
||||||
return tuple(paths)
|
return tuple(paths)
|
||||||
|
|
||||||
def get_module_utils(self, invocation):
|
def get_module_utils(self, invocation):
|
||||||
|
invocation.connection._connect()
|
||||||
module_utils = mitogen.service.call(
|
module_utils = mitogen.service.call(
|
||||||
context=invocation.connection.parent,
|
context=invocation.connection.parent,
|
||||||
handle=ansible_mitogen.services.ModuleDepService.handle,
|
handle=ansible_mitogen.services.ModuleDepService.handle,
|
||||||
|
@ -309,19 +319,7 @@ class NewStylePlanner(ScriptPlanner):
|
||||||
return module_utils, has_custom
|
return module_utils, has_custom
|
||||||
|
|
||||||
def plan(self, invocation):
|
def plan(self, invocation):
|
||||||
invocation.connection._connect()
|
|
||||||
module_utils, has_custom = self.get_module_utils(invocation)
|
module_utils, has_custom = self.get_module_utils(invocation)
|
||||||
mitogen.service.call(
|
|
||||||
context=invocation.connection.parent,
|
|
||||||
handle=ansible_mitogen.services.FileService.handle,
|
|
||||||
method='register_many',
|
|
||||||
kwargs={
|
|
||||||
'paths': [
|
|
||||||
path
|
|
||||||
for fullname, path, is_pkg in module_utils
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return super(NewStylePlanner, self).plan(
|
return super(NewStylePlanner, self).plan(
|
||||||
invocation,
|
invocation,
|
||||||
module_utils=module_utils,
|
module_utils=module_utils,
|
||||||
|
|
|
@ -151,12 +151,16 @@ class MuxProcess(object):
|
||||||
Construct a ContextService and a thread to service requests for it
|
Construct a ContextService and a thread to service requests for it
|
||||||
arriving from worker processes.
|
arriving from worker processes.
|
||||||
"""
|
"""
|
||||||
|
file_service = ansible_mitogen.services.FileService(router=self.router)
|
||||||
self.pool = mitogen.service.Pool(
|
self.pool = mitogen.service.Pool(
|
||||||
router=self.router,
|
router=self.router,
|
||||||
services=[
|
services=[
|
||||||
|
file_service,
|
||||||
ansible_mitogen.services.ContextService(self.router),
|
ansible_mitogen.services.ContextService(self.router),
|
||||||
ansible_mitogen.services.FileService(self.router),
|
ansible_mitogen.services.ModuleDepService(
|
||||||
ansible_mitogen.services.ModuleDepService(self.router),
|
router=self.router,
|
||||||
|
file_service=file_service,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
size=int(os.environ.get('MITOGEN_POOL_SIZE', '16')),
|
size=int(os.environ.get('MITOGEN_POOL_SIZE', '16')),
|
||||||
)
|
)
|
||||||
|
|
|
@ -38,6 +38,7 @@ how to build arguments for it, preseed related data, etc.
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import cStringIO
|
import cStringIO
|
||||||
import ctypes
|
import ctypes
|
||||||
|
import imp
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -182,6 +183,46 @@ class Runner(object):
|
||||||
self.revert()
|
self.revert()
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleUtilsImporter(object):
|
||||||
|
"""
|
||||||
|
:param list module_utils:
|
||||||
|
List of `(fullname, path, is_pkg)` tuples.
|
||||||
|
"""
|
||||||
|
def __init__(self, context, module_utils):
|
||||||
|
self._context = context
|
||||||
|
self._by_fullname = {
|
||||||
|
fullname: (path, is_pkg)
|
||||||
|
for fullname, path, is_pkg in module_utils
|
||||||
|
}
|
||||||
|
self._loaded = set()
|
||||||
|
sys.meta_path.insert(0, self)
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
sys.meta_path.remove(self)
|
||||||
|
for fullname in self._loaded:
|
||||||
|
sys.modules.pop(fullname, None)
|
||||||
|
|
||||||
|
def find_module(self, fullname, path=None):
|
||||||
|
if fullname in self._by_fullname:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def load_module(self, fullname):
|
||||||
|
path, is_pkg = self._by_fullname[fullname]
|
||||||
|
source = ansible_mitogen.target.get_file(self._context, path)
|
||||||
|
code = compile(source, path, 'exec')
|
||||||
|
mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
|
||||||
|
mod.__file__ = "master:%s" % (path,)
|
||||||
|
mod.__loader__ = self
|
||||||
|
if is_pkg:
|
||||||
|
mod.__path__ = []
|
||||||
|
mod.__package__ = fullname
|
||||||
|
else:
|
||||||
|
mod.__package__ = fullname.rpartition('.')[0]
|
||||||
|
exec(code, mod.__dict__)
|
||||||
|
self._loaded.add(fullname)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
class TemporaryEnvironment(object):
|
class TemporaryEnvironment(object):
|
||||||
def __init__(self, env=None):
|
def __init__(self, env=None):
|
||||||
self.original = os.environ.copy()
|
self.original = os.environ.copy()
|
||||||
|
@ -413,6 +454,10 @@ class NewStyleRunner(ScriptRunner):
|
||||||
# module, but this has never been a bug report. Instead act like an
|
# module, but this has never been a bug report. Instead act like an
|
||||||
# interpreter that had its script piped on stdin.
|
# interpreter that had its script piped on stdin.
|
||||||
self._argv = TemporaryArgv([''])
|
self._argv = TemporaryArgv([''])
|
||||||
|
self._importer = ModuleUtilsImporter(
|
||||||
|
context=self.service_context,
|
||||||
|
module_utils=self.module_utils,
|
||||||
|
)
|
||||||
if libc__res_init:
|
if libc__res_init:
|
||||||
libc__res_init()
|
libc__res_init()
|
||||||
|
|
||||||
|
|
|
@ -612,8 +612,9 @@ class ModuleDepService(mitogen.service.Service):
|
||||||
max_message_size = 1000
|
max_message_size = 1000
|
||||||
handle = 502
|
handle = 502
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, file_service, **kwargs):
|
||||||
super(ModuleDepService, self).__init__(*args, **kwargs)
|
super(ModuleDepService, self).__init__(**kwargs)
|
||||||
|
self._file_service = file_service
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
|
|
||||||
@mitogen.service.expose(policy=mitogen.service.AllowParents())
|
@mitogen.service.expose(policy=mitogen.service.AllowParents())
|
||||||
|
@ -630,4 +631,11 @@ class ModuleDepService(mitogen.service.Service):
|
||||||
search_path=search_path,
|
search_path=search_path,
|
||||||
)
|
)
|
||||||
self._cache[module_name, search_path] = resolved
|
self._cache[module_name, search_path] = resolved
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
self._file_service.register(path=path)
|
||||||
|
|
||||||
return self._cache[module_name, search_path]
|
return self._cache[module_name, search_path]
|
||||||
|
|
Loading…
Reference in New Issue