Merge remote-tracking branch 'origin/dmw'
- issue #411: fix check_host_keys="accept" - issue #305: dead message if max message size exceeded - issue #369: implement Connection.reset() - issue #76: disconnect propagation - log format string fixes - various 2/3 test fixes - large message benchmark - centralize stub client utils in data/stubs/ - activate faulthandler in tests - better OpenSSH 7.5+ permission denied handlingissue260
commit
9ba0561dd2
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Print shell environment exports adding ARA plugins to the list of plugins
|
||||||
|
from ansible.cfg in the CWD.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import ara.setup
|
||||||
|
import ansible.constants as C
|
||||||
|
|
||||||
|
os.chdir(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
print('export ANSIBLE_ACTION_PLUGINS=%s:%s' % (
|
||||||
|
':'.join(C.DEFAULT_ACTION_PLUGIN_PATH),
|
||||||
|
ara.setup.action_plugins,
|
||||||
|
))
|
||||||
|
|
||||||
|
print('export ANSIBLE_CALLBACK_PLUGINS=%s:%s' % (
|
||||||
|
':'.join(C.DEFAULT_CALLBACK_PLUGIN_PATH),
|
||||||
|
ara.setup.callback_plugins,
|
||||||
|
))
|
||||||
|
|
||||||
|
print('export ANSIBLE_LIBRARY=%s:%s' % (
|
||||||
|
':'.join(C.DEFAULT_MODULE_PATH),
|
||||||
|
ara.setup.library,
|
||||||
|
))
|
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
- hosts: all
|
||||||
|
any_errors_fatal: true
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
- name: Create file tree
|
||||||
|
connection: local
|
||||||
|
shell: >
|
||||||
|
mkdir -p /tmp/filetree.in;
|
||||||
|
for i in `seq -f /tmp/filetree.in/%g 1 100`; do echo $RANDOM > $i; done;
|
||||||
|
|
||||||
|
- name: Delete remote file tree
|
||||||
|
file: path=/tmp/filetree.out state=absent
|
||||||
|
when: 0
|
||||||
|
|
||||||
|
- file:
|
||||||
|
state: directory
|
||||||
|
path: /tmp/filetree.out
|
||||||
|
|
||||||
|
- name: Trigger nasty process pileup
|
||||||
|
copy:
|
||||||
|
src: "{{item.src}}"
|
||||||
|
dest: "/tmp/filetree.out/{{item.path}}"
|
||||||
|
with_filetree: /tmp/filetree.in
|
||||||
|
when: item.state == 'file'
|
@ -0,0 +1,25 @@
|
|||||||
|
z
|
||||||
|
|
||||||
|
[z-x10]
|
||||||
|
z-[01:10]
|
||||||
|
|
||||||
|
[z-x20]
|
||||||
|
z-[01:20]
|
||||||
|
|
||||||
|
[z-x50]
|
||||||
|
z-[01:50]
|
||||||
|
|
||||||
|
[z-x100]
|
||||||
|
z-[001:100]
|
||||||
|
|
||||||
|
[z-x200]
|
||||||
|
z-[001:200]
|
||||||
|
|
||||||
|
[z-x300]
|
||||||
|
z-[001:300]
|
||||||
|
|
||||||
|
[z-x400]
|
||||||
|
z-[001:400]
|
||||||
|
|
||||||
|
[z-x500]
|
||||||
|
z-[001:500]
|
@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
- import_playbook: disconnect_during_module.yml
|
||||||
|
- import_playbook: disconnect_resets_connection.yml
|
||||||
- import_playbook: exec_command.yml
|
- import_playbook: exec_command.yml
|
||||||
- import_playbook: put_small_file.yml
|
|
||||||
- import_playbook: put_large_file.yml
|
- import_playbook: put_large_file.yml
|
||||||
|
- import_playbook: put_small_file.yml
|
||||||
|
- import_playbook: reset.yml
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
# issue 352: test ability to notice disconnection during a module invocation.
|
||||||
|
---
|
||||||
|
|
||||||
|
- name: integration/connection/disconnect_during_module.yml
|
||||||
|
hosts: test-targets localhost
|
||||||
|
gather_facts: no
|
||||||
|
any_errors_fatal: false
|
||||||
|
tasks:
|
||||||
|
- run_once: true # don't run against localhost
|
||||||
|
shell: |
|
||||||
|
kill -9 $PPID
|
||||||
|
register: out
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- out.msg.startswith('Mitogen was disconnected from the remote environment while a call was in-progress.')
|
||||||
|
|
||||||
|
- meta: clear_host_errors
|
@ -0,0 +1,44 @@
|
|||||||
|
# issue 370: Connection should reset to 'disconnected' state when disconnect
|
||||||
|
# detected
|
||||||
|
#
|
||||||
|
# Previously the 'Mitogen was disconnected' error would fail the first task,
|
||||||
|
# but the Connection instance would still think it still had a valid
|
||||||
|
# connection.
|
||||||
|
#
|
||||||
|
# See also disconnect_during_module.yml
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- name: integration/connection/disconnect_resets_connection.yml
|
||||||
|
hosts: test-targets
|
||||||
|
gather_facts: no
|
||||||
|
any_errors_fatal: true
|
||||||
|
tasks:
|
||||||
|
- mitogen_action_script:
|
||||||
|
script: |
|
||||||
|
import sys
|
||||||
|
from ansible.errors import AnsibleConnectionFailure
|
||||||
|
|
||||||
|
assert not self._connection.connected, \
|
||||||
|
"Connection was not initially disconnected."
|
||||||
|
|
||||||
|
self._low_level_execute_command('echo')
|
||||||
|
assert self._connection.connected, \
|
||||||
|
"Connection was not connected after good command."
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._low_level_execute_command('kill -9 $PPID')
|
||||||
|
assert 0, 'AnsibleConnectionFailure was not raised'
|
||||||
|
except AnsibleConnectionFailure:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
assert str(e).startswith('Mitogen was disconnected')
|
||||||
|
|
||||||
|
assert not self._connection.connected, \
|
||||||
|
"Connection did not reset."
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._low_level_execute_command('kill -9 $PPID')
|
||||||
|
assert 0, 'AnsibleConnectionFailure was not raised'
|
||||||
|
except AnsibleConnectionFailure:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
assert str(e).startswith('Mitogen was disconnected')
|
@ -0,0 +1,38 @@
|
|||||||
|
# issue #369: Connection.reset() should cause destruction of the remote
|
||||||
|
# interpreter and any children.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- name: integration/connection/reset.yml
|
||||||
|
hosts: test-targets
|
||||||
|
tasks:
|
||||||
|
- when: is_mitogen
|
||||||
|
block:
|
||||||
|
- custom_python_detect_environment:
|
||||||
|
register: out
|
||||||
|
|
||||||
|
- custom_python_detect_environment:
|
||||||
|
become: true
|
||||||
|
register: out_become
|
||||||
|
|
||||||
|
- meta: reset_connection
|
||||||
|
|
||||||
|
- custom_python_detect_environment:
|
||||||
|
register: out2
|
||||||
|
|
||||||
|
- custom_python_detect_environment:
|
||||||
|
register: out_become2
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
# Interpreter PID has changed.
|
||||||
|
- out.pid != out2.pid
|
||||||
|
|
||||||
|
# SSH PID has changed.
|
||||||
|
- out.ppid != out2.ppid
|
||||||
|
|
||||||
|
# Interpreter PID has changed.
|
||||||
|
- out_become.pid != out_become2.pid
|
||||||
|
|
||||||
|
# sudo PID has changed.
|
||||||
|
- out_become.ppid != out_become2.ppid
|
@ -0,0 +1,28 @@
|
|||||||
|
# I am an Ansible action plug-in. I run the script provided in the parameter in
|
||||||
|
# the context of the action.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ansible.plugins.action import ActionBase
|
||||||
|
|
||||||
|
|
||||||
|
def execute(s, gbls, lcls):
|
||||||
|
if sys.version_info > (3,):
|
||||||
|
exec(s, gbls, lcls)
|
||||||
|
else:
|
||||||
|
exec('exec s in gbls, lcls')
|
||||||
|
|
||||||
|
|
||||||
|
class ActionModule(ActionBase):
|
||||||
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
super(ActionModule, self).run(tmp=tmp, task_vars=task_vars)
|
||||||
|
lcls = {
|
||||||
|
'self': self,
|
||||||
|
'result': {}
|
||||||
|
}
|
||||||
|
execute(self._task.args['script'], globals(), lcls)
|
||||||
|
return lcls['result']
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -0,0 +1,2 @@
|
|||||||
|
paramiko==2.3.2 # Last 2.6-compat version.
|
||||||
|
google-api-python-client==1.6.5
|
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
# tests/bench/
|
||||||
|
|
||||||
|
Various manually executed scripts to aid benchmarking, or trigger old
|
||||||
|
performance problems.
|
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
# Verify _receive_one() quadratic behaviour fixed.
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import mitogen
|
||||||
|
|
||||||
|
|
||||||
|
@mitogen.main()
|
||||||
|
def main(router):
|
||||||
|
c = router.fork()
|
||||||
|
|
||||||
|
n = 1048576 * 127
|
||||||
|
s = ' ' * n
|
||||||
|
print('bytes in %.2fMiB string...' % (n/1048576.0),)
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
for x in range(10):
|
||||||
|
tt0 = time.time()
|
||||||
|
assert n == c.call(len, s)
|
||||||
|
print('took %dms' % (1000 * (time.time() - tt0),))
|
||||||
|
t1 = time.time()
|
||||||
|
print('total %dms / %dms avg / %.2fMiB/sec' % (
|
||||||
|
1000 * (t1 - t0),
|
||||||
|
(1000 * (t1 - t0)) / (x + 1),
|
||||||
|
((n * (x + 1)) / (t1 - t0)) / 1048576.0,
|
||||||
|
))
|
@ -0,0 +1,23 @@
|
|||||||
|
"""
|
||||||
|
Measure latency of local service RPC.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import mitogen.service
|
||||||
|
import mitogen
|
||||||
|
|
||||||
|
|
||||||
|
class MyService(mitogen.service.Service):
|
||||||
|
@mitogen.service.expose(policy=mitogen.service.AllowParents())
|
||||||
|
def ping(self):
|
||||||
|
return 'pong'
|
||||||
|
|
||||||
|
|
||||||
|
@mitogen.main()
|
||||||
|
def main(router):
|
||||||
|
f = router.fork()
|
||||||
|
t0 = time.time()
|
||||||
|
for x in range(1000):
|
||||||
|
f.call_service(service_name=MyService, method_name='ping')
|
||||||
|
print('++', int(1e6 * ((time.time() - t0) / (1.0+x))), 'usec')
|
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
import testlib
|
||||||
|
|
||||||
|
import mitogen.core
|
||||||
|
|
||||||
|
|
||||||
|
class DeferSyncTest(testlib.TestCase):
|
||||||
|
klass = mitogen.core.Broker
|
||||||
|
|
||||||
|
def test_okay(self):
|
||||||
|
broker = self.klass()
|
||||||
|
try:
|
||||||
|
th = broker.defer_sync(lambda: threading.currentThread())
|
||||||
|
self.assertEquals(th, broker._thread)
|
||||||
|
finally:
|
||||||
|
broker.shutdown()
|
||||||
|
|
||||||
|
def test_exception(self):
|
||||||
|
broker = self.klass()
|
||||||
|
try:
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
broker.defer_sync, lambda: int('dave'))
|
||||||
|
finally:
|
||||||
|
broker.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest2.main()
|
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
import logging
|
||||||
|
import mitogen.master
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
router = mitogen.master.Router()
|
||||||
|
|
||||||
|
l = router.local()
|
||||||
|
l.call(foo)
|
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
# tests/data/stubs/
|
||||||
|
|
||||||
|
Dummy implementations of various third party tools that just spawn local Python
|
||||||
|
interpreters. Used to roughly test the tools' associated Mitogen classes.
|
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
os.environ['ORIGINAL_ARGV'] = repr(sys.argv)
|
||||||
|
os.execv(sys.executable, sys.argv[sys.argv.index('-c') - 1:])
|
@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import mitogen
|
||||||
|
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
import testlib
|
||||||
|
|
||||||
|
|
||||||
|
class ConstructorTest(testlib.RouterMixin, unittest2.TestCase):
|
||||||
|
def test_okay(self):
|
||||||
|
docker_path = testlib.data_path('stubs/docker.py')
|
||||||
|
context = self.router.docker(
|
||||||
|
container='container_name',
|
||||||
|
docker_path=docker_path,
|
||||||
|
)
|
||||||
|
stream = self.router.stream_by_id(context.context_id)
|
||||||
|
|
||||||
|
argv = eval(context.call(os.getenv, 'ORIGINAL_ARGV'))
|
||||||
|
self.assertEquals(argv[0], docker_path)
|
||||||
|
self.assertEquals(argv[1], 'exec')
|
||||||
|
self.assertEquals(argv[2], '--interactive')
|
||||||
|
self.assertEquals(argv[3], 'container_name')
|
||||||
|
self.assertEquals(argv[4], stream.python_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest2.main()
|
@ -0,0 +1,119 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
import mitogen
|
||||||
|
import mitogen.fork
|
||||||
|
import mitogen.master
|
||||||
|
import mitogen.service
|
||||||
|
import mitogen.unix
|
||||||
|
|
||||||
|
import testlib
|
||||||
|
|
||||||
|
|
||||||
|
class MyService(mitogen.service.Service):
|
||||||
|
def __init__(self, latch, **kwargs):
|
||||||
|
super(MyService, self).__init__(**kwargs)
|
||||||
|
# used to wake up main thread once client has made its request
|
||||||
|
self.latch = latch
|
||||||
|
|
||||||
|
@mitogen.service.expose(policy=mitogen.service.AllowParents())
|
||||||
|
def ping(self, msg):
|
||||||
|
self.latch.put(None)
|
||||||
|
return {
|
||||||
|
'src_id': msg.src_id,
|
||||||
|
'auth_id': msg.auth_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IsPathDeadTest(unittest2.TestCase):
|
||||||
|
func = staticmethod(mitogen.unix.is_path_dead)
|
||||||
|
path = '/tmp/stale-socket'
|
||||||
|
|
||||||
|
def test_does_not_exist(self):
|
||||||
|
self.assertTrue(self.func('/tmp/does-not-exist'))
|
||||||
|
|
||||||
|
def make_socket(self):
|
||||||
|
if os.path.exists(self.path):
|
||||||
|
os.unlink(self.path)
|
||||||
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
s.bind(self.path)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def test_conn_refused(self):
|
||||||
|
s = self.make_socket()
|
||||||
|
s.close()
|
||||||
|
self.assertTrue(self.func(self.path))
|
||||||
|
|
||||||
|
def test_is_alive(self):
|
||||||
|
s = self.make_socket()
|
||||||
|
s.listen(5)
|
||||||
|
self.assertFalse(self.func(self.path))
|
||||||
|
s.close()
|
||||||
|
os.unlink(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
class ListenerTest(testlib.RouterMixin, unittest2.TestCase):
|
||||||
|
klass = mitogen.unix.Listener
|
||||||
|
|
||||||
|
def test_constructor_basic(self):
|
||||||
|
listener = self.klass(router=self.router)
|
||||||
|
self.assertFalse(mitogen.unix.is_path_dead(listener.path))
|
||||||
|
os.unlink(listener.path)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientTest(unittest2.TestCase):
|
||||||
|
klass = mitogen.unix.Listener
|
||||||
|
|
||||||
|
def _try_connect(self, path):
|
||||||
|
# give server a chance to setup listener
|
||||||
|
for x in range(10):
|
||||||
|
try:
|
||||||
|
return mitogen.unix.connect(path)
|
||||||
|
except socket.error:
|
||||||
|
if x == 9:
|
||||||
|
raise
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def _test_simple_client(self, path):
|
||||||
|
router, context = self._try_connect(path)
|
||||||
|
self.assertEquals(0, context.context_id)
|
||||||
|
self.assertEquals(1, mitogen.context_id)
|
||||||
|
self.assertEquals(0, mitogen.parent_id)
|
||||||
|
resp = context.call_service(service_name=MyService, method_name='ping')
|
||||||
|
self.assertEquals(mitogen.context_id, resp['src_id'])
|
||||||
|
self.assertEquals(0, resp['auth_id'])
|
||||||
|
|
||||||
|
def _test_simple_server(self, path):
|
||||||
|
router = mitogen.master.Router()
|
||||||
|
latch = mitogen.core.Latch()
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
listener = self.klass(path=path, router=router)
|
||||||
|
pool = mitogen.service.Pool(router=router, services=[
|
||||||
|
MyService(latch=latch, router=router),
|
||||||
|
])
|
||||||
|
latch.get()
|
||||||
|
# give broker a chance to deliver service resopnse
|
||||||
|
time.sleep(0.1)
|
||||||
|
finally:
|
||||||
|
pool.shutdown()
|
||||||
|
router.broker.shutdown()
|
||||||
|
finally:
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
path = mitogen.unix.make_socket_path()
|
||||||
|
if os.fork():
|
||||||
|
self._test_simple_client(path)
|
||||||
|
else:
|
||||||
|
mitogen.fork.on_fork()
|
||||||
|
self._test_simple_server(path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest2.main()
|
Loading…
Reference in New Issue