From 1acb0ae53923cb89b722edff4d314aefd6b81c79 Mon Sep 17 00:00:00 2001 From: Omer Date: Thu, 14 Dec 2023 17:22:44 +0200 Subject: [PATCH] chore(RHTAPWATCH-634): request ODCS compose Implement ODCSRequestor, allowing requesting ODCS for multiple compose files, per number of sources. Signed-off-by: Omer --- generate_compose/compose_generator.py | 2 +- .../odcs_configurations_generator.py | 20 ++++- generate_compose/odcs_fetcher.py | 4 +- generate_compose/odcs_requester.py | 23 ++++- tests/test_odcs_fetcher.py | 4 +- tests/test_odcs_requester.py | 83 +++++++++++++++++++ 6 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 tests/test_odcs_requester.py diff --git a/generate_compose/compose_generator.py b/generate_compose/compose_generator.py index cdd8c8c..4a69dc0 100644 --- a/generate_compose/compose_generator.py +++ b/generate_compose/compose_generator.py @@ -13,7 +13,7 @@ @dataclass(frozen=True) class ComposeGenerator: """ - Given implementations to all building blocks if a compose generator, generate a + Given implementations to all building blocks of a compose generator, generate a compose and return its reference. :param configurations_generator: an object to generate the configurations used for diff --git a/generate_compose/odcs_configurations_generator.py b/generate_compose/odcs_configurations_generator.py index 192368b..3ef53d8 100644 --- a/generate_compose/odcs_configurations_generator.py +++ b/generate_compose/odcs_configurations_generator.py @@ -1,15 +1,29 @@ """Configurations generator for ODCS compose""" -from dataclasses import dataclass +from dataclasses import dataclass, field + +from odcs.client.odcs import ComposeSourceGeneric # type: ignore from .protocols import ComposeConfigurations, ComposeConfigurationsGenerator @dataclass(frozen=True) -class ODCSConfigurations(ComposeConfigurations): +class ODCSComposeConfig: """ Configurations to be used for requesting an ODCS compose """ + spec: ComposeSourceGeneric + additional_args: dict = field(default_factory=dict) + + +@dataclass +class ODCSComposesConfigs(ComposeConfigurations): + """ + Configurations to be used for requesting multiple ODCS composes + """ + + configs: list[ODCSComposeConfig] + @dataclass(frozen=True) class ODCSConfigurationsGenerator(ComposeConfigurationsGenerator): @@ -21,5 +35,5 @@ class ODCSConfigurationsGenerator(ComposeConfigurationsGenerator): compose_inputs: dict - def __call__(self) -> ODCSConfigurations: + def __call__(self) -> ComposeConfigurations: raise NotImplementedError() diff --git a/generate_compose/odcs_fetcher.py b/generate_compose/odcs_fetcher.py index 6f2bab3..97a541a 100644 --- a/generate_compose/odcs_fetcher.py +++ b/generate_compose/odcs_fetcher.py @@ -5,7 +5,7 @@ import requests -from .odcs_requester import ODCSRequestReference +from .odcs_requester import ODCSRequestReferences from .protocols import ComposeFetcher, ComposeReference @@ -39,7 +39,7 @@ def __call__(self, request_reference: ComposeReference) -> ODCSResultReference: :return: The filesystem path to the downloaded ODCS compose file. """ self.compose_dir_path.mkdir(parents=True, exist_ok=True) - assert isinstance(request_reference, ODCSRequestReference) + assert isinstance(request_reference, ODCSRequestReferences) urls = request_reference.compose_urls for url in urls: with tempfile.NamedTemporaryFile( diff --git a/generate_compose/odcs_requester.py b/generate_compose/odcs_requester.py index d6e6724..3b30e6c 100644 --- a/generate_compose/odcs_requester.py +++ b/generate_compose/odcs_requester.py @@ -1,11 +1,14 @@ """Request a new ODCS compose""" from dataclasses import dataclass +from odcs.client.odcs import ODCS # type: ignore + +from .odcs_configurations_generator import ODCSComposesConfigs from .protocols import ComposeConfigurations, ComposeReference, ComposeRequester @dataclass(frozen=True) -class ODCSRequestReference(ComposeReference): +class ODCSRequestReferences(ComposeReference): """ Reference to a remotely-stored compose data """ @@ -16,9 +19,21 @@ class ODCSRequestReference(ComposeReference): @dataclass(frozen=True) class ODCSRequester(ComposeRequester): """ - Request a new ODCS compose based on compose configurations and return a reference + Request a new ODCS composes based on compose configurations and return a reference to the remote compose location. """ - def __call__(self, configs: ComposeConfigurations) -> ODCSRequestReference: - raise NotImplementedError() + odcs: ODCS = ODCS("https://odcs.engineering.redhat.com/") + + def __call__(self, compose_configs: ComposeConfigurations) -> ODCSRequestReferences: + assert isinstance(compose_configs, ODCSComposesConfigs) + composes = [ + self.odcs.request_compose(config.spec, **config.additional_args) + for config in compose_configs.configs + ] + for compose in composes: + self.odcs.wait_for_compose(compose["id"]) + + compose_urls = [compose["result_repofile"] for compose in composes] + req_refrences = ODCSRequestReferences(compose_urls=compose_urls) + return req_refrences diff --git a/tests/test_odcs_fetcher.py b/tests/test_odcs_fetcher.py index 43a5670..af3fe33 100644 --- a/tests/test_odcs_fetcher.py +++ b/tests/test_odcs_fetcher.py @@ -6,7 +6,7 @@ import responses from generate_compose.odcs_fetcher import ODCSFetcher, ODCSResultReference -from generate_compose.odcs_requester import ODCSRequestReference +from generate_compose.odcs_requester import ODCSRequestReferences @pytest.mark.parametrize( @@ -46,7 +46,7 @@ ) def test_odcs_fetcher(tmp_path: Path, composes: list[str], urls: list[str]) -> None: """test ODCSFetcher.__call__""" - req_ref = ODCSRequestReference(compose_urls=urls) + req_ref = ODCSRequestReferences(compose_urls=urls) fetcher = ODCSFetcher(compose_dir_path=tmp_path) with responses.RequestsMock() as mock: diff --git a/tests/test_odcs_requester.py b/tests/test_odcs_requester.py new file mode 100644 index 0000000..4822f7d --- /dev/null +++ b/tests/test_odcs_requester.py @@ -0,0 +1,83 @@ +"""test_odcs_requester.py - test odcs_requester""" +from typing import Callable + +# pylint: disable=redefined-outer-name +from unittest.mock import MagicMock, create_autospec + +import pytest +from odcs.client.odcs import ODCS, ComposeSourceTag # type: ignore + +from generate_compose.odcs_configurations_generator import ( + ODCSComposeConfig, + ODCSComposesConfigs, +) +from generate_compose.odcs_requester import ODCSRequester, ODCSRequestReferences + + +@pytest.fixture() +def compose_url() -> str: + """Example compose-url, as close to the one expected""" + return "http://download.eng.bos.redhat.com/odcs/prod/odcs-222222" + + +@pytest.fixture() +def create_odcs_mock(compose_url: str) -> Callable[[int], MagicMock]: + """Create an ODCS mock with specific results for the compose method""" + + def _mock_odcs_compose(num_of_composes: int) -> MagicMock: + """Monkey-patched ODCS compose""" + mock: MagicMock = create_autospec(ODCS) + mock.request_compose.side_effect = [ + {"result_repofile": f"{compose_url}", "id": i} + for i in range(1, num_of_composes + 1) + ] + return mock + + return _mock_odcs_compose + + +@pytest.fixture() +def compose_source_tag() -> ComposeSourceTag: + """Example ComposeSource of type 'tag'""" + return ComposeSourceTag(tag="tag") + + +@pytest.mark.parametrize( + ("composes_configs, num_of_composes"), + [ + pytest.param( + ODCSComposesConfigs([ODCSComposeConfig(spec=ComposeSourceTag(tag="tag"))]), + 1, + id="single compose, tag source, no additional arguments", + ), + pytest.param( + ODCSComposesConfigs( + [ + ODCSComposeConfig(spec=ComposeSourceTag(tag="tag")), + ODCSComposeConfig(spec=ComposeSourceTag(tag="tag")), + ] + ), + 2, + id="multiple composes, , tag source, no additional arguments", + ), + ], +) +def test_odcs_requester( + create_odcs_mock: Callable, + compose_url: str, + compose_source_tag: ComposeSourceTag, + composes_configs: ODCSComposesConfigs, + num_of_composes: int, +) -> None: + """test ODCSRequester.__call__""" + expected_req_ref = ODCSRequestReferences( + compose_urls=[compose_url] * num_of_composes + ) + mock_odcs = create_odcs_mock(num_of_composes) + odcs_requester = ODCSRequester(odcs=mock_odcs) + req_ref = odcs_requester(compose_configs=composes_configs) + + assert mock_odcs.request_compose.call_count == num_of_composes + assert mock_odcs.request_compose.call_args[0][0].source == compose_source_tag.source + assert mock_odcs.wait_for_compose.call_count == num_of_composes + assert req_ref == expected_req_ref