Merge remote-tracking branch 'origin/dmw'

* origin/dmw:
  issue #633: skip test on older Ansibles.
  issue #633: update Changelog.
  issue #633: various task_vars fixes
  issue #633: handle meta: reset_connection when become is active
  issue #633: take inventory_hostname from task_vars
  docs: , -> :
  docs: use new manpage alias in one more place
new-serialization
David Wilson 5 years ago
commit 99c5cece3a

@ -56,6 +56,12 @@ import ansible_mitogen.transport_config
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
task_vars_msg = (
'could not recover task_vars. This means some connection '
'settings may erroneously be reset to their defaults. '
'Please report a bug if you encounter this message.'
)
def get_remote_name(spec): def get_remote_name(spec):
""" """
@ -486,15 +492,9 @@ class Connection(ansible.plugins.connection.ConnectionBase):
# the case of the synchronize module. # the case of the synchronize module.
# #
#: Set to the host name as it appears in inventory by on_action_run().
inventory_hostname = None
#: Set to task_vars by on_action_run(). #: Set to task_vars by on_action_run().
_task_vars = None _task_vars = None
#: Set to 'hostvars' by on_action_run()
host_vars = None
#: Set by on_action_run() #: Set by on_action_run()
delegate_to_hostname = None delegate_to_hostname = None
@ -527,12 +527,10 @@ class Connection(ansible.plugins.connection.ConnectionBase):
:param str loader_basedir: :param str loader_basedir:
Loader base directory; see :attr:`loader_basedir`. Loader base directory; see :attr:`loader_basedir`.
""" """
self.inventory_hostname = task_vars['inventory_hostname']
self._task_vars = task_vars self._task_vars = task_vars
self.host_vars = task_vars['hostvars']
self.delegate_to_hostname = delegate_to_hostname self.delegate_to_hostname = delegate_to_hostname
self.loader_basedir = loader_basedir self.loader_basedir = loader_basedir
self._mitogen_reset(mode='put') self._put_connection()
def _get_task_vars(self): def _get_task_vars(self):
""" """
@ -552,8 +550,10 @@ class Connection(ansible.plugins.connection.ConnectionBase):
for new connections to be constructed in addition to the preconstructed for new connections to be constructed in addition to the preconstructed
connection passed into any running action. connection passed into any running action.
""" """
f = sys._getframe() if self._task_vars is not None:
return self._task_vars
f = sys._getframe()
while f: while f:
if f.f_code.co_name == 'run': if f.f_code.co_name == 'run':
f_locals = f.f_locals f_locals = f.f_locals
@ -571,9 +571,23 @@ class Connection(ansible.plugins.connection.ConnectionBase):
f = f.f_back f = f.f_back
LOG.warning('could not recover task_vars. This means some connection ' raise ansible.errors.AnsibleConnectionFailure(task_vars_msg)
'settings may erroneously be reset to their defaults. '
'Please report a bug if you encounter this message.') def get_host_vars(self, inventory_hostname):
"""
Fetch the HostVars for a host.
:returns:
Variables dictionary or :data:`None`.
:raises ansible.errors.AnsibleConnectionFailure:
Task vars unavailable.
"""
task_vars = self._get_task_vars()
hostvars = task_vars.get('hostvars')
if hostvars:
return hostvars.get(inventory_hostname)
raise ansible.errors.AnsibleConnectionFailure(task_vars_msg)
def get_task_var(self, key, default=None): def get_task_var(self, key, default=None):
""" """
@ -586,17 +600,16 @@ class Connection(ansible.plugins.connection.ConnectionBase):
does not make sense to extract connection-related configuration for the does not make sense to extract connection-related configuration for the
delegated-to machine from them. delegated-to machine from them.
""" """
task_vars = self._task_vars or self._get_task_vars() task_vars = self._get_task_vars()
if task_vars is not None: if self.delegate_to_hostname is None:
if self.delegate_to_hostname is None: if key in task_vars:
return task_vars[key]
else:
delegated_vars = task_vars['ansible_delegated_vars']
if self.delegate_to_hostname in delegated_vars:
task_vars = delegated_vars[self.delegate_to_hostname]
if key in task_vars: if key in task_vars:
return task_vars[key] return task_vars[key]
else:
delegated_vars = task_vars['ansible_delegated_vars']
if self.delegate_to_hostname in delegated_vars:
task_vars = delegated_vars[self.delegate_to_hostname]
if key in task_vars:
return task_vars[key]
return default return default
@ -628,7 +641,8 @@ class Connection(ansible.plugins.connection.ConnectionBase):
# must use __contains__ to avoid a TypeError for a missing host on # must use __contains__ to avoid a TypeError for a missing host on
# Ansible 2.3. # Ansible 2.3.
if self.host_vars is None or inventory_name not in self.host_vars: via_vars = self.get_host_vars(inventory_name)
if via_vars is None:
raise ansible.errors.AnsibleConnectionFailure( raise ansible.errors.AnsibleConnectionFailure(
self.unknown_via_msg % ( self.unknown_via_msg % (
via_spec, via_spec,
@ -636,7 +650,6 @@ class Connection(ansible.plugins.connection.ConnectionBase):
) )
) )
via_vars = self.host_vars[inventory_name]
return ansible_mitogen.transport_config.MitogenViaSpec( return ansible_mitogen.transport_config.MitogenViaSpec(
inventory_name=inventory_name, inventory_name=inventory_name,
play_context=self._play_context, play_context=self._play_context,
@ -712,7 +725,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
connection=self, connection=self,
play_context=self._play_context, play_context=self._play_context,
transport=self.transport, transport=self.transport,
inventory_name=self.inventory_hostname, inventory_name=self.get_task_var('inventory_hostname'),
) )
stack = self._stack_from_spec(spec) stack = self._stack_from_spec(spec)
return spec.inventory_name(), stack return spec.inventory_name(), stack
@ -781,15 +794,11 @@ class Connection(ansible.plugins.connection.ConnectionBase):
self.binding = worker_model.get_binding(inventory_name) self.binding = worker_model.get_binding(inventory_name)
self._connect_stack(stack) self._connect_stack(stack)
def _mitogen_reset(self, mode): def _put_connection(self):
""" """
Forget everything we know about the connected context. This function Forget everything we know about the connected context. This function
cannot be called _reset() since that name is used as a public API by cannot be called _reset() since that name is used as a public API by
Ansible 2.4 wait_for_connection plug-in. Ansible 2.4 wait_for_connection plug-in.
:param str mode:
Name of ContextService method to use to discard the context, either
'put' or 'reset'.
""" """
if not self.context: if not self.context:
return return
@ -798,7 +807,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
mitogen.service.call( mitogen.service.call(
call_context=self.binding.get_service_context(), call_context=self.binding.get_service_context(),
service_name='ansible_mitogen.services.ContextService', service_name='ansible_mitogen.services.ContextService',
method_name=mode, method_name='put',
context=self.context context=self.context
) )
@ -813,24 +822,11 @@ class Connection(ansible.plugins.connection.ConnectionBase):
gracefully shut down, and wait for shutdown to complete. Safe to call gracefully shut down, and wait for shutdown to complete. Safe to call
multiple times. multiple times.
""" """
self._mitogen_reset(mode='put') self._put_connection()
if self.binding: if self.binding:
self.binding.close() self.binding.close()
self.binding = None self.binding = None
def _reset_find_task_vars(self):
"""
Monsterous hack: since "meta: reset_connection" does not run from an
action, we cannot capture task variables via :meth:`on_action_run`.
Instead walk the parent frames searching for the `all_vars` local from
StrategyBase._execute_meta(). If this fails, just leave task_vars
unset, likely causing a subtly wrong configuration to be selected.
"""
frame = sys._getframe()
while frame and not self._task_vars:
self._task_vars = frame.f_locals.get('all_vars')
frame = frame.f_back
reset_compat_msg = ( reset_compat_msg = (
'Mitogen only supports "reset_connection" on Ansible 2.5.6 or later' 'Mitogen only supports "reset_connection" on Ansible 2.5.6 or later'
) )
@ -842,9 +838,6 @@ class Connection(ansible.plugins.connection.ConnectionBase):
the 'disconnected' state, and informs ContextService the connection is the 'disconnected' state, and informs ContextService the connection is
bad somehow, and should be shut down and discarded. bad somehow, and should be shut down and discarded.
""" """
if self._task_vars is None:
self._reset_find_task_vars()
if self._play_context.remote_addr is None: if self._play_context.remote_addr is None:
# <2.5.6 incorrectly populate PlayContext for reset_connection # <2.5.6 incorrectly populate PlayContext for reset_connection
# https://github.com/ansible/ansible/issues/27520 # https://github.com/ansible/ansible/issues/27520
@ -852,10 +845,24 @@ class Connection(ansible.plugins.connection.ConnectionBase):
self.reset_compat_msg self.reset_compat_msg
) )
self._connect() # Clear out state in case we were ever connected.
self._mitogen_reset(mode='reset') self.close()
self.binding.close()
self.binding = None inventory_name, stack = self._build_stack()
if self._play_context.become:
stack = stack[:-1]
worker_model = ansible_mitogen.process.get_worker_model()
binding = worker_model.get_binding(inventory_name)
try:
mitogen.service.call(
call_context=binding.get_service_context(),
service_name='ansible_mitogen.services.ContextService',
method_name='reset',
stack=mitogen.utils.cast(list(stack)),
)
finally:
binding.close()
# Compatibility with Ansible 2.4 wait_for_connection plug-in. # Compatibility with Ansible 2.4 wait_for_connection plug-in.
_reset = reset _reset = reset

@ -156,20 +156,41 @@ class ContextService(mitogen.service.Service):
@mitogen.service.expose(mitogen.service.AllowParents()) @mitogen.service.expose(mitogen.service.AllowParents())
@mitogen.service.arg_spec({ @mitogen.service.arg_spec({
'context': mitogen.core.Context 'stack': list,
}) })
def reset(self, context): def reset(self, stack):
""" """
Return a reference, forcing close and discard of the underlying Return a reference, forcing close and discard of the underlying
connection. Used for 'meta: reset_connection' or when some other error connection. Used for 'meta: reset_connection' or when some other error
is detected. is detected.
:returns:
:data:`True` if a connection was found to discard, otherwise
:data:`False`.
""" """
LOG.debug('%r.reset(%r)', self, context) LOG.debug('%r.reset(%r)', self, stack)
self._lock.acquire()
try: l = mitogen.core.Latch()
context = None
with self._lock:
for i, spec in enumerate(stack):
key = key_from_dict(via=context, **spec)
response = self._response_by_key.get(key)
if response is None:
LOG.debug('%r: could not find connection to shut down; '
'failed at hop %d', self, i)
return False
context = response['context']
mitogen.core.listen(context, 'disconnect', l.put)
self._shutdown_unlocked(context) self._shutdown_unlocked(context)
finally:
self._lock.release() # The timeout below is to turn a hang into a crash in case there is any
# possible race between 'disconnect' signal subscription, and the child
# abruptly disconnecting.
l.get(timeout=30.0)
return True
@mitogen.service.expose(mitogen.service.AllowParents()) @mitogen.service.expose(mitogen.service.AllowParents())
@mitogen.service.arg_spec({ @mitogen.service.arg_spec({

@ -21,7 +21,16 @@ v0.2.9 (unreleased)
To avail of fixes in an unreleased version, please download a ZIP file To avail of fixes in an unreleased version, please download a ZIP file
`directly from GitHub <https://github.com/dw/mitogen/>`_. `directly from GitHub <https://github.com/dw/mitogen/>`_.
*(no changes)* * :gh:issue:`633`: :ans:mod:`meta: reset_connection <meta>` could fail to reset
a connection when ``become: true`` was set on the playbook.
Thanks!
~~~~~~~
Mitogen would not be possible without the support of users. A huge thanks for
bug reports, testing, features and fixes in this release contributed by
`Can Ozokur httpe://github.com/canozokur/>`_,
v0.2.8 (2019-08-18) v0.2.8 (2019-08-18)
@ -41,7 +50,7 @@ Enhancements
<https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html>`_ <https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html>`_
are not yet handled. are not yet handled.
* :gh:issue:`419`, :gh:issue:`470`, file descriptor usage is approximately * :gh:issue:`419`, :gh:issue:`470`: file descriptor usage is approximately
halved, as it is no longer necessary to separately manage read and write halved, as it is no longer necessary to separately manage read and write
sides to work around a design problem. sides to work around a design problem.
@ -80,7 +89,7 @@ Mitogen for Ansible
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
* :gh:issue:`363`: fix an obscure race matching *Permission denied* errors from * :gh:issue:`363`: fix an obscure race matching *Permission denied* errors from
some versions of ``su`` running on heavily loaded machines. some versions of :linux:man1:`su` running on heavily loaded machines.
* :gh:issue:`410`: Uses of :linux:man7:`unix` sockets are replaced with * :gh:issue:`410`: Uses of :linux:man7:`unix` sockets are replaced with
traditional :linux:man7:`pipe` pairs when SELinux is detected, to work around traditional :linux:man7:`pipe` pairs when SELinux is detected, to work around
@ -266,7 +275,7 @@ Thanks!
Mitogen would not be possible without the support of users. A huge thanks for Mitogen would not be possible without the support of users. A huge thanks for
bug reports, testing, features and fixes in this release contributed by bug reports, testing, features and fixes in this release contributed by
`Andreas Hubert <https://github.com/peshay>`_. `Andreas Hubert <https://github.com/peshay>`_,
`Anton Markelov <https://github.com/strangeman>`_, `Anton Markelov <https://github.com/strangeman>`_,
`Dan <https://github.com/dsgnr>`_, `Dan <https://github.com/dsgnr>`_,
`Dave Cottlehuber <https://github.com/dch>`_, `Dave Cottlehuber <https://github.com/dch>`_,

@ -8,3 +8,4 @@
- include: put_large_file.yml - include: put_large_file.yml
- include: put_small_file.yml - include: put_small_file.yml
- include: reset.yml - include: reset.yml
- include: reset_become.yml

@ -0,0 +1,48 @@
# issue #633: Connection.reset() should ignore "become", and apply to the login
# account.
- hosts: test-targets
become: true
gather_facts: false
tasks:
- debug: msg="reset_become.yml skipped on Ansible<2.5.6"
when: ansible_version.full < '2.5.6'
- meta: end_play
when: ansible_version.full < '2.5.6'
- name: save pid of the become acct
custom_python_detect_environment:
register: become_acct
- name: save pid of the login acct
become: false
custom_python_detect_environment:
register: login_acct
- name: ensure login != become
assert:
that:
- become_acct.pid != login_acct.pid
- name: reset the connection
meta: reset_connection
- name: save new pid of the become acct
custom_python_detect_environment:
register: new_become_acct
- name: ensure become_acct != new_become_acct
assert:
that:
- become_acct.pid != new_become_acct.pid
- name: save new pid of login acct
become: false
custom_python_detect_environment:
register: new_login_acct
- name: ensure login_acct != new_login_acct
assert:
that:
- login_acct.pid != new_login_acct.pid

@ -46,7 +46,13 @@ class ConnectionMixin(MuxProcessMixin):
def make_connection(self): def make_connection(self):
play_context = ansible.playbook.play_context.PlayContext() play_context = ansible.playbook.play_context.PlayContext()
return self.klass(play_context, new_stdin=False) conn = self.klass(play_context, new_stdin=False)
conn.on_action_run(
task_vars={},
delegate_to_hostname=None,
loader_basedir=None,
)
return conn
def wait_for_completion(self): def wait_for_completion(self):
# put_data() is asynchronous, must wait for operation to happen. Do # put_data() is asynchronous, must wait for operation to happen. Do

Loading…
Cancel
Save