Skip to content

Commit

Permalink
chore(RHTAPWATCH-634): request ODCS compose
Browse files Browse the repository at this point in the history
Implement ODCSRequestor, allowing requesting ODCS
for multiple compose files, per number of sources.

Signed-off-by: Omer <[email protected]>
  • Loading branch information
Omeramsc committed Dec 17, 2023
1 parent 9b2d4b7 commit 1acb0ae
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 12 deletions.
2 changes: 1 addition & 1 deletion generate_compose/compose_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 17 additions & 3 deletions generate_compose/odcs_configurations_generator.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -21,5 +35,5 @@ class ODCSConfigurationsGenerator(ComposeConfigurationsGenerator):

compose_inputs: dict

def __call__(self) -> ODCSConfigurations:
def __call__(self) -> ComposeConfigurations:
raise NotImplementedError()
4 changes: 2 additions & 2 deletions generate_compose/odcs_fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import requests

from .odcs_requester import ODCSRequestReference
from .odcs_requester import ODCSRequestReferences
from .protocols import ComposeFetcher, ComposeReference


Expand Down Expand Up @@ -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(
Expand Down
23 changes: 19 additions & 4 deletions generate_compose/odcs_requester.py
Original file line number Diff line number Diff line change
@@ -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
"""
Expand All @@ -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
4 changes: 2 additions & 2 deletions tests/test_odcs_fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down
83 changes: 83 additions & 0 deletions tests/test_odcs_requester.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 1acb0ae

Please sign in to comment.