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

Implement image pull policies #233

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

Implement image pull policies #233

wants to merge 4 commits into from

Conversation

thedrow
Copy link
Collaborator

@thedrow thedrow commented Aug 21, 2022

These policies exist in testcontainers-java and are currently missing from this project.
They allow you to specify when you should pull a new image.

@thedrow
Copy link
Collaborator Author

thedrow commented Aug 23, 2022

@tillahoffmann This is ready for review. Do you mind taking a look?

Copy link
Collaborator

@tillahoffmann tillahoffmann left a comment

Choose a reason for hiding this comment

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

Looks good, I've left some inline comments.

One larger question is whether we need the additional complexity of the image cache. Directly getting the images from the client takes about 5 ms on my machine. That seems negligible compared with typical runtimes of tests because of the container start-up time. Removing the cache could simplify the code a little. What do you think?

@@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with python 3.6
# This file is autogenerated by pip-compile with python 3.10
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should probably be regenerated with 3.6.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm getting:

pip-compile --output-file=requirements/3.6.txt requirements.in
  ERROR: Could not find a version that satisfies the requirement python-arango==7.4.1 (from testcontainers===dev->-r requirements.in (line 1)) (from versions: 3.0.0, 3.1.0, 3.2.0, 3.2.2, 3.3.0, 3.4.0, 3.4.1, 3.5.0, 3.6.0, 3.7.0, 3.8.0, 3.9.0, 3.10.0, 3.10.1, 3.11.0, 3.12.1, 4.0.0, 4.0.1, 4.1.0, 4.2.0, 4.2.1, 4.3.0, 4.4.0, 5.0.0, 5.1.0, 5.2.0, 5.2.1, 5.3.0, 5.4.0, 6.0.0, 6.1.0, 7.0.0, 7.0.1, 7.1.0, 7.2.0, 7.3.0, 7.3.1)
Traceback (most recent call last):
  File "/Users/okatz6/.pyenv/versions/3.6.15/bin/pip-compile", line 11, in <module>
    sys.exit(cli())
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/click/core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/click/core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/click/core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/click/decorators.py", line 26, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/piptools/scripts/compile.py", line 466, in cli
    results = resolver.resolve(max_rounds=max_rounds)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/piptools/resolver.py", line 175, in resolve
    has_changed, best_matches = self._resolve_one_round()
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/piptools/resolver.py", line 319, in _resolve_one_round
    their_constraints.extend(self._iter_dependencies(best_match))
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/piptools/resolver.py", line 428, in _iter_dependencies
    dependencies = self.repository.get_dependencies(ireq)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/piptools/repositories/local.py", line 79, in get_dependencies
    return self.repository.get_dependencies(ireq)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/piptools/repositories/pypi.py", line 237, in get_dependencies
    download_dir, ireq, wheel_cache
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/piptools/repositories/pypi.py", line 199, in resolve_reqs
    results = resolver._resolve_one(reqset, ireq)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/pip/_internal/resolution/legacy/resolver.py", line 379, in _resolve_one
    dist = self._get_dist_for(req_to_install)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/pip/_internal/resolution/legacy/resolver.py", line 331, in _get_dist_for
    self._populate_link(req)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/pip/_internal/resolution/legacy/resolver.py", line 300, in _populate_link
    req.link = self._find_requirement_link(req)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/pip/_internal/resolution/legacy/resolver.py", line 266, in _find_requirement_link
    best_candidate = self.finder.find_requirement(req, upgrade)
  File "/Users/okatz6/.pyenv/versions/3.6.15/lib/python3.6/site-packages/pip/_internal/index/package_finder.py", line 910, in find_requirement
    "No matching distribution found for {}".format(req)
pip._internal.exceptions.DistributionNotFound: No matching distribution found for python-arango==7.4.1 (from testcontainers===dev->-r requirements.in (line 1))

Any idea why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

This should do the trick.

# Targets to build requirement files
requirements : ${REQUIREMENTS}
${REQUIREMENTS} : requirements/%.txt : requirements.in setup.py
mkdir -p $(dir $@)
${RUN} -w /workspace -v `pwd`:/workspace python:$* bash -c \
"pip install pip-tools && pip-compile -v --upgrade -o $@ $<"

If not, let's drop 3.6 as you suggested in another issue.

requirements/3.7.txt Outdated Show resolved Hide resolved
requirements/3.8.txt Outdated Show resolved Hide resolved
requirements/3.9.txt Outdated Show resolved Hide resolved
testcontainers/core/policies/_cache.py Show resolved Hide resolved
only if it does not exist locally
"""

def should_pull_cached(self, image: Image) -> bool:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a no-op, right? We could just write DefaultPullPolicy = ImagePullPolicy or drop one of them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because ImagePullPolicy is abstract.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My hunch is that we can remove the notion of an abstract ImagePullPolicy because python uses duck-typing (as opposed to more interface-heavy Java, for example). Some discussion here.



def test_default_pull_policy_cached_image():
ImagePullPolicy.IMAGES_CACHE = MagicMock(spec=_LocalImagesCache)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use with unittest.mock.patch here to make sure the original values are restored after the test is done?

@codecov-commenter
Copy link

codecov-commenter commented Aug 25, 2022

Codecov Report

Merging #233 (798baa0) into master (95fb5e0) will increase coverage by 0.79%.
The diff coverage is 95.12%.

@@            Coverage Diff             @@
##           master     #233      +/-   ##
==========================================
+ Coverage   86.32%   87.12%   +0.79%     
==========================================
  Files          27       33       +6     
  Lines         746      823      +77     
  Branches       70       81      +11     
==========================================
+ Hits          644      717      +73     
- Misses         79       82       +3     
- Partials       23       24       +1     
Impacted Files Coverage Δ
testcontainers/core/container.py 76.69% <80.00%> (-0.39%) ⬇️
testcontainers/core/policies/_cache.py 92.30% <92.30%> (ø)
testcontainers/core/docker_client.py 53.33% <100.00%> (+2.17%) ⬆️
testcontainers/core/policies/__init__.py 100.00% <100.00%> (ø)
testcontainers/core/policies/always.py 100.00% <100.00%> (ø)
testcontainers/core/policies/base.py 100.00% <100.00%> (ø)
testcontainers/core/policies/default.py 100.00% <100.00%> (ø)
testcontainers/core/policies/maxage.py 100.00% <100.00%> (ø)
testcontainers/mssql.py 100.00% <0.00%> (ø)
testcontainers/mysql.py 84.00% <0.00%> (ø)
... and 6 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@thedrow
Copy link
Collaborator Author

thedrow commented Aug 30, 2022

Looks good, I've left some inline comments.

One larger question is whether we need the additional complexity of the image cache. Directly getting the images from the client takes about 5 ms on my machine. That seems negligible compared with typical runtimes of tests because of the container start-up time. Removing the cache could simplify the code a little. What do you think?

I ported the Java code to work and the cache is present there, which is why it is present here. Note that the same container might be stopped or started multiple times and 5ms for a single query regarding its age might add up to a lot in a very large test suite.
If the cache can shave off these calls to Docker, a test suite of significant size should benefit from this cache.

@tillahoffmann
Copy link
Collaborator

I have a relatively strong preference to remove the cache for two reasons. First, it's more code to maintain. Second, I don't think the caching will help in most real-world scenarios.

I appreciate that runtime is different across different machines, but getting a single image takes about 4ms on my machine. Using the cache, getting a single image takes about 300ms. It'll of course be quicker the next time images are fetched, but we'd have to use at least 75 containers per test suite to make the cache worthwhile. To have a delay of one second over the cached version, we'd have to run about 325 containers. I'd argue that for a test suite that runs more than 300 docker containers a delay of one second is negligible. But let me know if I misunderstood/miscalculated.

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

Successfully merging this pull request may close these issues.

None yet

3 participants