Skip to content

Commit c9ae9be

Browse files
committed
RHOAIENG-20088: chore(tests/containers): check images size change
Adds a new test to our pytest set to check the given image size compared to the expected value. In this test we are checking the uncompressed image size by summing up all the layeres of the image. This image is usually downloaded on the machine where this is being run already. https://issues.redhat.com/browse/RHOAIENG-20088
1 parent 65aed92 commit c9ae9be

File tree

1 file changed

+67
-5
lines changed

1 file changed

+67
-5
lines changed

tests/containers/base_image_test.py

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ def _run_test(self, image: str, test_fn: Callable[[DockerContainer],_]):
3737
test_fn(container)
3838
return
3939
except Exception as e:
40-
pytest.fail(f"Unexpected exception in test: {e}")
40+
pytest.fail(f"Unexpected exception in test: {e}")
4141
finally:
4242
docker_utils.NotebookContainer(container).stop(timeout=0)
4343

4444
# If the return doesn't happen in the try block, fail the test
4545
pytest.fail("The test did not pass as expected.")
46-
46+
4747

4848
def test_elf_files_can_link_runtime_libs(self, subtests: pytest_subtests.SubTests, image):
49-
49+
5050
def test_fn(container: DockerContainer):
5151
def check_elf_file():
5252
"""This python function will be executed on the image itself.
@@ -123,7 +123,7 @@ def check_elf_file():
123123
continue # it's in ../
124124

125125
with subtests.test(f"{dlib=}"):
126-
pytest.fail(f"{dlib=} has unsatisfied dependencies {deps=}")
126+
pytest.fail(f"{dlib=} has unsatisfied dependencies {deps=}")
127127

128128
self._run_test(image=image, test_fn=test_fn)
129129

@@ -145,7 +145,7 @@ def test_fn(container: DockerContainer):
145145
logging.debug(output.decode())
146146
assert ecode == 0
147147

148-
self._run_test(image=image, test_fn=test_fn)
148+
self._run_test(image=image, test_fn=test_fn)
149149

150150
def test_pip_install_cowsay_runs(self, image: str):
151151
"""Checks that the Python virtualenv in the image is writable."""
@@ -219,6 +219,68 @@ def test_oc_command_runs_fake_fips(self, image: str, subtests: pytest_subtests.S
219219
finally:
220220
docker_utils.NotebookContainer(container).stop(timeout=0)
221221

222+
# There are two ways how the image is being updated
223+
# 1. A change to the image is being done (e.g. package update, Dockerfile update etc.). This is what this test does.
224+
# In this case, we need to check the size of the build image that contains these updates. We're checking the compressed image size.
225+
# 2. A change is done to the params.env file or runtimes images definitions, where we update manifest references to a new image.
226+
# Check for this scenario is being done in 'ci/[check-params-env.sh|check-runtime-images.sh]'.
227+
size_treshold: int = 100 # in MBs
228+
percent_treshold: int = 10
229+
def test_image_size_change(self, image: str):
230+
f"""Checks the image size didn't change extensively - treshold is {self.percent_treshold}% or {self.size_treshold} MB."""
231+
232+
# Map of image label names with expected size in MBs.
233+
expected_image_name_size_map = {
234+
"odh-notebook-base-centos-stream9-python-3.11": 1350,
235+
"odh-notebook-base-ubi9-python-3.11": 1262,
236+
"odh-notebook-cuda-c9s-python-3.11": 11519,
237+
"odh-notebook-cuda-ubi9-python-3.11": 9070, # TODO
238+
"odh-notebook-jupyter-datascience-ubi9-python-3.11": 2845,
239+
"odh-notebook-jupyter-minimal-ubi9-python-3.11": 1472, # gpu 9070; rocm 26667 ???
240+
"odh-notebook-jupyter-pytorch-ubi9-python-3.11": 15444,
241+
"odh-notebook-cuda-jupyter-tensorflow-ubi9-python-3.11": 15218,
242+
"odh-notebook-jupyter-trustyai-ubi9-python-3.11": 8613,
243+
"odh-notebook-jupyter-rocm-pytorch-ubi9-python-3.11": 33001,
244+
"odh-notebook-jupyter-rocm-tensorflow-ubi9-python-3.11": 30241,
245+
"odh-notebook-rstudio-server-c9s-python-3.11": 13201, # 3221 ??
246+
"odh-notebook-runtime-datascience-ubi9-python-3.11": 2518,
247+
"odh-notebook-runtime-minimal-ubi9-python-3.11": 1362,
248+
"odh-notebook-runtime-pytorch-ubi9-python-3.11": 7487,
249+
"odh-notebook-cuda-runtime-tensorflow-ubi9-python-3.11": 14572,
250+
"odh-notebook-runtime-rocm-pytorch-ubi9-python-3.11": 32682,
251+
"odh-notebook-rocm-runtime-tensorflow-ubi9-python-3.11": 29805,
252+
"odh-notebook-code-server-ubi9-python-3.11": 2598,
253+
"odh-notebook-rocm-python-3.11": 26667, # TODO
254+
}
255+
256+
import docker
257+
client = testcontainers.core.container.DockerClient()
258+
try:
259+
image_metadata = client.client.images.get(image)
260+
except docker.errors.ImageNotFound:
261+
image_metadata = client.client.images.pull(image)
262+
assert isinstance(image_metadata, docker.models.images.Image)
263+
264+
actual_img_size = image_metadata.attrs["Size"]
265+
actual_img_size = round(actual_img_size / 1024 / 1024)
266+
logging.info(f"The size of the image is {actual_img_size} MBs.")
267+
logging.debug(f"The image metadata: {image_metadata}")
268+
269+
img_label_name = image_metadata.labels["name"]
270+
if img_label_name in expected_image_name_size_map:
271+
expected_img_size = expected_image_name_size_map[img_label_name]
272+
logging.debug(f"Expected size of the '{img_label_name}' image is {expected_img_size} MBs.")
273+
else:
274+
pytest.fail(f"Image name label '{img_label_name}' is not in the expected image size map {expected_image_name_size_map}")
275+
276+
# Check the size change constraints now
277+
# 1. Percentual size change
278+
abs_percent_change = abs(actual_img_size / expected_img_size * 100 - 100)
279+
assert abs_percent_change < self.percent_treshold, f"Image size of '{img_label_name}' changed by {abs_percent_change}% (expected: {expected_img_size} MB; actual: {actual_img_size} MB; treshold: {self.percent_treshold}%)."
280+
# 2. Absolute size change
281+
abs_size_difference = abs(actual_img_size - expected_img_size)
282+
assert abs_size_difference < self.size_treshold, f"Image size of '{img_label_name}' changed by {abs_size_difference} MB (expected: {expected_img_size} MB; actual: {actual_img_size} MB; treshold: {self.size_treshold} MB)."
283+
222284
def encode_python_function_execution_command_interpreter(python: str, function: Callable[..., Any], *args: list[Any]) -> list[str]:
223285
"""Returns a cli command that will run the given Python function encoded inline.
224286
All dependencies (imports, ...) must be part of function body."""

0 commit comments

Comments
 (0)