Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to disable streaming to stdout file #937

Merged
merged 3 commits into from Dec 15, 2021

Conversation

AlanCoding
Copy link
Member

This addresses #926

My current intention is to make use of the setting (set it to True) in AWX, unconditionally. We don't want the additional disk writes this incurs. If the executor team is okay with the looks of this, I may ask @amolgautam25 to make that PR.

This use case is only concerned with the pexpect runner mode. I only messed with subprocess here for completeness. The subprocess mode also appears to somehow be connected with the introduction of this behavior. Maybe @ganeshrn has more history to share about this.

@AlanCoding AlanCoding requested a review from a team as a code owner December 7, 2021 21:03
@github-actions github-actions bot added the needs_triage New item that needs to be triaged label Dec 7, 2021
@github-actions github-actions bot added the docs Changes to documentation label Dec 7, 2021
@@ -139,7 +139,8 @@ The **settings** file is a little different than the other files provided in thi
* ``pexpect_timeout``: ``10`` Number of seconds for the internal pexpect command to wait to block on input before continuing
* ``pexpect_use_poll``: ``True`` Use ``poll()`` function for communication with child processes instead of ``select()``. ``select()`` is used when the value is set to ``False``. ``select()`` has a known limitation of using only up to 1024 file descriptors.

* ``suppress_ansible_output``: ``False`` Allow output from ansible to not be printed to the screen
* ``suppress_output_file``: ``False`` Allow output from ansible to not be streamed to the ``stdout`` or ``stderr`` files inside of the artifacts directory.
* ``suppress_ansible_output``: ``False`` Allow output from ansible to not be printed to the screen.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a check to make these mutually exclusive? If they are both set ansible would "just run" with no output, maybe we don't care?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AWX will set them both to False by design. The output is processed by the callback receiver. If someone really wanted the standard out file (which is normally deleted at the end of the run anyway) then we can introduce a setting for them to get it back.

There's a feature in AWX to download the standard out file from a job run. It is illustrative that this works by a completely unrelated method. In the request-response cycle, it is written to disk from the database data and then delivered to the client. Then deleted.

# about being nice
m = mocker.patch('time.sleep')
m.start()
request.addfinalizer(m.stop)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the timing, and the whole module takes about 3.6 seconds. Maybe this was reducing it to 3.5 or 3.4, but the difference also could have been indistinguishable. I'm using time.sleep to make the assertion reliable, so that's why I did this.

Copy link
Contributor

@Shrews Shrews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good to me, other than the comment on the tests to re-use the iterate_timeout() function.

stdout_path = Path(rc.artifact_dir) / 'stdout'
# this can be subject to a race condition so we will be patient with the check
start_time = time.time()
while time.time() - start_time < 2.0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use iterate_timeout() here from test/utils/common.py. You can find examples in the test code.

@@ -154,13 +155,9 @@ def test_stdout_file_write(rc, runner_mode):
assert status == 'successful'
stdout_path = Path(rc.artifact_dir) / 'stdout'
# this can be subject to a race condition so we will be patient with the check
start_time = time.time()
while time.time() - start_time < 2.0:
for _ in iterate_timeout(2.0, 'stdout file to be written', interval=0.2):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can get some pretty slow test VMs, so 2 seconds can be cutting it pretty darn close, which is likely why you see the test failure. I see no reason not to just bump this to, say, 30 seconds.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The first time I checked results I saw 2 configurations failing and after adding the 2 second poll 1 was failing. So this would fit the picture that it's slow, dependent on the hardware.

@Shrews Shrews added gate and removed needs_triage New item that needs to be triaged labels Dec 13, 2021
Copy link
Contributor

@ansible-zuul ansible-zuul bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@ansible-zuul ansible-zuul bot removed the gate label Dec 14, 2021
@AlanCoding
Copy link
Member Author

Interesting... I'm wondering if this was a pre-existing failure of the same type

=================================== FAILURES ===================================
�[31m�[1m_____________________________ test_password_prompt _____________________________�[0m
[gw3] linux -- Python 3.8.12 /home/zuul/src/github.com/ansible/ansible-runner/.tox/py38/bin/python

rc = <ansible_runner.config.runner.RunnerConfig object at 0x7ff864f2e520>

    def test_password_prompt(rc):
        rc.command = [sys.executable, '-c' 'import time; print(input("Password: "))']
        rc.expect_passwords[re.compile(r'Password:\s*?$', re.M)] = '1234'
        status, exitcode = Runner(config=rc).run()
        assert status == 'successful'
        assert exitcode == 0
        with open(os.path.join(rc.artifact_dir, 'stdout')) as f:
>           assert '1234' in f.read()
�[1m�[31mE           AssertionError: assert '1234' in ''�[0m
�[1m�[31mE            +  where '' = <built-in method read of _io.TextIOWrapper object at 0x7ff864d44ee0>()�[0m
�[1m�[31mE            +    where <built-in method read of _io.TextIOWrapper object at 0x7ff864d44ee0> = <_io.TextIOWrapper name='/tmp/pytest-of-zuul/pytest-0/popen-gw3/test_password_prompt0/artifacts/21213e93-55b2-4899-8dc7-edeebb7c59ec/stdout' mode='r' encoding='UTF-8'>.read�[0m

exitcode   = 0
f          = <_io.TextIOWrapper name='/tmp/pytest-of-zuul/pytest-0/popen-gw3/test_password_prompt0/artifacts/21213e93-55b2-4899-8dc7-edeebb7c59ec/stdout' mode='r' encoding='UTF-8'>
rc         = <ansible_runner.config.runner.RunnerConfig object at 0x7ff864f2e520>
status     = 'successful'

�[1m�[31mtest/integration/test_runner.py�[0m:22: AssertionError

I can apply the same methods to that assertion. That looks like it may be the proper thing to do.

@Shrews
Copy link
Contributor

Shrews commented Dec 15, 2021

The disk space issue aside (no idea why we're seeing that on occasion), each time the new test_stdout_file_write has failed has been with the pexpect path (not the subprocess path). That may be worth looking into.

@Shrews
Copy link
Contributor

Shrews commented Dec 15, 2021

The disk space issue aside (no idea why we're seeing that on occasion), each time the new test_stdout_file_write has failed has been with the pexpect path (not the subprocess path). That may be worth looking into.

FYI, the failure using pexpect is reproducible on Linux. It can take several runs, but definitely reproducible.

Copy link
Contributor

@Shrews Shrews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing approval until the test failure under pexpect is figured out.

@AlanCoding
Copy link
Member Author

Would it be a problem for me to do a pytest.skip for the pexpect case? It looks like it's just buggy from some problem with the library maybe.

Do a quick poll until assertion is satisfied
Use iterate_timeout test util from review comment
reuse poll for the stdout file because of race condition
Skip test for pexpect because it does not work
@AlanCoding
Copy link
Member Author

@Shrews I added the skip, which looks like it's working to stabilize tests. I can make an issue to cover the stdout log file not working with pexpect

Copy link
Contributor

@Shrews Shrews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes me a sad panda that we have to skip the test case we explicitly want this feature for, but this change obviously didn't create the error, only exposed it. So I guess skipping it is ok for now.

@Shrews Shrews added the gate label Dec 15, 2021
@ansible-zuul ansible-zuul bot merged commit cb7a971 into ansible:devel Dec 15, 2021
ansible-zuul bot pushed a commit that referenced this pull request Jan 4, 2022
Fix suppress_output_file bug where event_callback was not called

Sorry, I had a major flaw with #937 and I should have tested that better before it was merged.
The handle was not passed to pexpect, and that was wrong. The handle is the way that it dispatches events to the event_callback. Even if you do not intend to write to the stdout file, this is necessary.
Design-wise, if suppress_output_file is given, we do want to use the stdout handle wrapper, but we do not want to use the stdout handle itself. I think this code structure articulates that about as best as possible. Alternatively, we could create a null handler and pass that to OutputEventFilter, instead of handling the None case.

Separately, I may have discovered a bug related to the test failures on the prior PR.

  
    
      ansible-runner/ansible_runner/utils/__init__.py
    
    
         Line 373
      in
      d013044
    
    
    
    

        
          
           event_data['stdout'] = stdout_chunk[:-2] if len(stdout_chunk) > 2 else "" 
        
    
  


This is assuming the .splitlines(True) leaves \r\n at the end of the line. This is... not universal. Because of that, the final character in the echo command gets truncated, because it has a \n without the \r. This looks completely fixable, but I wish to keep that separate.

Reviewed-by: David Shrewsbury <None>
Reviewed-by: None <None>
Reviewed-by: Alexander Sowitzki <dev@eqrx.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Changes to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants