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

DockerRunner: Enable background container to run commands against #51

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

DockerRunner: Enable background container to run commands against #51

wants to merge 9 commits into from

Conversation

AbcSxyZ
Copy link
Contributor

@AbcSxyZ AbcSxyZ commented Dec 17, 2021

Following a comment where @patricklodder was speaking about having a mechanism to start/stop a container for the entire test.

Implemented it in the idea to add some tests who can need it, at least datadir test from #50.

It changes the way commands are proceeded : A single background container is started for the entire test of a TestRunner subclass, and all commands for the test are executed with docker exec (calling the entrypoint directly).

Run CI tests

Will need some documentation to facilitate usage, but for now it's possible to run it using:

python3 -m tests.integration_runner --platform amd64 --image [dogecoin-image] --version 1.14.5 [--verbose]

@AbcSxyZ AbcSxyZ closed this Dec 18, 2021
@AbcSxyZ AbcSxyZ reopened this Dec 18, 2021
@patricklodder
Copy link
Member

Is it possible to retain the single-shot run method so that we can separate framework updates from test updates?

@AbcSxyZ
Copy link
Contributor Author

AbcSxyZ commented Dec 18, 2021

Sure

@AbcSxyZ AbcSxyZ marked this pull request as draft December 18, 2021 14:27
@patricklodder patricklodder added the qa Such quality label Dec 18, 2021
@AbcSxyZ AbcSxyZ marked this pull request as ready for review December 18, 2021 19:24
@AbcSxyZ
Copy link
Contributor Author

AbcSxyZ commented Dec 18, 2021

I restored run commands. I used exec everywhere for now, where would you use run ? What kind of difference are you thinking of ?

@patricklodder
Copy link
Member

What kind of difference are you thinking of ?

Currently, there is a difference between exec and run: run uses entry point which de-escalates the uid, exec does not have an entry point and we don't USER right now, so everything there is root. This yields different results so we need to retain both until we have a better security solution.

Additionally, you're changing the version test right now, that's not needed with this enhancement.

@AbcSxyZ
Copy link
Contributor Author

AbcSxyZ commented Dec 18, 2021

Exec commands are also using entrypoint. Using verbose to show tests commands:

$ docker exec -e VERSION=1 c5a168d2a634dde84 entrypoint.py dogecoind
$ docker exec c5a168d2a634dde84 entrypoint.py dogecoin-cli -?
$ docker exec c5a168d2a634dde84 entrypoint.py dogecoin-tx -?

Actually, I also add the entrypoint manually when using docker run, it's done in _construct_command:

def _construct_command(self, docker_cmd, envs, args):

EDIT: My runs commands sucks

With my fix, docker run launch the following commands:

$ docker run -e VERSION=1 dogecoin entrypoint.py dogecoind                                                                                                                      
$ docker run dogecoin entrypoint.py dogecoin-cli -?                                                                                                                             
$ docker run dogecoin entrypoint.py dogecoin-tx -?     

@AbcSxyZ AbcSxyZ marked this pull request as draft December 18, 2021 23:16
@AbcSxyZ AbcSxyZ marked this pull request as ready for review December 18, 2021 23:25
@patricklodder
Copy link
Member

Exec commands are also using entrypoint. Using verbose to show tests commands:

That's not what we'd recommend a user to do though, right? I'll later on comment on that when I review the code.

@AbcSxyZ
Copy link
Contributor Author

AbcSxyZ commented Dec 18, 2021

Yes, it builds manually the command created by ENTRYPOINT + CMD.


def __del__(self):
"""Remove test container"""
stop_command = ["docker", "rm", "-f", self.container_id]
Copy link
Member

Choose a reason for hiding this comment

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

If we want to retain the container (to investigate because an error happened) it's useful to have a 2-step process: docker stop the container, and then docker rm the container. We can then choose to not rm on test failure, allowing a docker start and perhaps exec into a shell to investigate.

Can we split this up into 2 commands that are called independent from the destructor?

Copy link
Contributor Author

@AbcSxyZ AbcSxyZ Dec 19, 2021

Choose a reason for hiding this comment

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

With our 2 type of command, exec & run:

  • run commands outputs (stdout + stderr) are direclty handled. Stdout is returned, stderr displayed when error occurs (see DockerRunner._shell). Logs are already caught.
  • exec commands do not add any logs, they seem to handle only main process outputs.

We can do this, but logs are already displayed, not sure where we would use docker log.

stop_command = ["docker", "rm", "-f", self.container_id]
self._shell(stop_command, silent=True)

def _start(self):
Copy link
Member

Choose a reason for hiding this comment

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

Why protected?

Copy link
Contributor Author

@AbcSxyZ AbcSxyZ Dec 19, 2021

Choose a reason for hiding this comment

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

Just a habit because it wasn't used by outside. It was designed with the idea that DockerRunner will always start a container for a test. If the design change and we do not use it necessary as you suggest, it needs to change.

Copy link
Member

Choose a reason for hiding this comment

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

I'm okay with it when it's designed to not be ran from outside ever because <reason>, but I do not really agree with the reasoning.

It was designed with the idea that DockerRunner will always start a container for a test.

That's a design change of DockerRunner introduced with this PR, my comment is based on original design that is more permissive. The reason why I didn't design it like that with the initial build of the framework is because I can think of tests where we want to just docker run a single command and check the output, to check the exact, automated behavior of that (as was done with the version test), or, to run multiple docker containers to test an end-to-end use case, e.g. doing development on regtest, where there are scenarios where you need at least 2 nodes that connect to each other, for example when you want to work with getblocktemplate or auxpow RPC interfaces.

tests/integration/framework/docker_runner.py Outdated Show resolved Hide resolved

def run_interactive_command(self, envs, args):
Copy link
Member

Choose a reason for hiding this comment

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

could you please bring this method back so that the tests don't have to change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Related to #51 (comment)

Comment on lines 58 to 60
self.container = DockerRunner(self.options.platform,
self.options.image, self.options.verbose)

Copy link
Member

Choose a reason for hiding this comment

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

this forces all tests to run/exec a container. should leave it up to the individual tests in their run_test method, by putting this in a separate method that can be overridden by child classes and then call that method here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fine

Copy link
Contributor Author

@AbcSxyZ AbcSxyZ Dec 19, 2021

Choose a reason for hiding this comment

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

I changed it, the line is still (almost) the same, but it does not start automatically a background container.

It will start automatically on the first docker exec command, making background containers optional without the need to start the container manually when writing tests.

Copy link
Member

Choose a reason for hiding this comment

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

what if I want to change the way the container starts? i.e. test for permission escalation?

Copy link
Contributor Author

@AbcSxyZ AbcSxyZ Dec 20, 2021

Choose a reason for hiding this comment

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

Any example ? No idea how to perform this kind of test.

But you have the docker run interface as normally, and even if using entrypoint through docker exec any commands will run as root (outside of Dogecoin executables), as entrypoint is designed to accept arbitrary bash command.

@@ -25,15 +25,22 @@ def run_test(self):
"""Actual test, must be implemented by the final class"""
raise NotImplementedError

def run_command(self, envs, args):
Copy link
Member

Choose a reason for hiding this comment

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

Please restore this so that version test doesn't have to change

@@ -27,21 +27,21 @@ def run_test(self):
self.version_expr = re.compile(f".*{ self.options.version }.*")

# check dogecoind with only env
dogecoind = self.run_command(["VERSION=1"], [])
Copy link
Member

Choose a reason for hiding this comment

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

Please revert all changes to this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using self.docker_run would be fine for you ?
Also, methods TestRunner.docker_run and TestRunner.docker_exec return directly the output to avoid the use of .stdout access & encoding/decoding stuff. Error messages are directly handled on command execution (DockerRunner._shell) and create test failure, not used in tests (at least for now). You would like to restore this also ?

@AbcSxyZ
Copy link
Contributor Author

AbcSxyZ commented Dec 19, 2021

I did some fixes to reproduce real docker run commands. For versions tests with each command launched with both run and exec, it generates the following:

version
----------------------
$ docker run -e VERSION=1 --platform amd64 dogecoin
$ docker exec -e VERSION=1 acc889cd4db4eba2 entrypoint.py dogecoind
$ docker run --platform amd64 dogecoin dogecoin-cli -?
$ docker exec acc889cd4db4eba2 entrypoint.py dogecoin-cli -?
$ docker run --platform amd64 dogecoin dogecoin-tx -?
$ docker exec acc889cd4db4eba2 entrypoint.py dogecoin-tx -?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
qa Such quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants