Skip to content

Commit

Permalink
Run anycastd from MainConfiguration using the run command (#26)
Browse files Browse the repository at this point in the history
* Add function to convert `ServiceConfiguration` to `Service` instance

* Raname `config_to_instance` and improve docstring

* Expose `config_to_service` from configuration module instead of `_sub_config_to_instance`

* Expose `run_from_configuration` from core module

* Call `run_from_configuration` in CLI module

* Add tests for run CLI command

* Fix typo "pararmeters"
  • Loading branch information
SRv6d authored Dec 6, 2023
1 parent 6704818 commit 4a727ca
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 10 deletions.
3 changes: 3 additions & 0 deletions src/anycastd/_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from anycastd import __version__
from anycastd._cli.output import ExitCode, print_error
from anycastd._configuration import ConfigurationError, MainConfiguration
from anycastd.core import run_from_configuration

CONFIG_PATH = Path("/etc/anycastd/config.toml")

Expand Down Expand Up @@ -51,6 +52,8 @@ def run(
] = CONFIG_PATH,
) -> None:
"""Run anycastd."""
main_configuration = _get_main_configuration(config)
run_from_configuration(main_configuration)


def _get_main_configuration(config: Path) -> MainConfiguration:
Expand Down
2 changes: 1 addition & 1 deletion src/anycastd/_configuration/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from anycastd._configuration.conversion import config_to_instance
from anycastd._configuration.conversion import config_to_service
from anycastd._configuration.exceptions import ConfigurationError
from anycastd._configuration.main import MainConfiguration
39 changes: 35 additions & 4 deletions src/anycastd/_configuration/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,56 @@
HealthcheckConfiguration,
)
from anycastd._configuration.prefix import FRRPrefixConfiguration, PrefixConfiguration
from anycastd._configuration.service import ServiceConfiguration
from anycastd._executor import LocalExecutor
from anycastd.core._service import Service
from anycastd.healthcheck import CabourotteHealthcheck, Healthcheck
from anycastd.prefix import FRRoutingPrefix, Prefix


def config_to_service(config: ServiceConfiguration) -> Service:
"""Convert a service configuration to an actual service instance.
Args:
config: The configuration to convert.
Returns:
A service instance with the parameters from the configuration.
"""
prefixes: tuple[Prefix, ...] = tuple(
_sub_config_to_instance(prefix) for prefix in config.prefixes
)
health_checks: tuple[Healthcheck, ...] = tuple(
_sub_config_to_instance(check) for check in config.checks
)

return Service(name=config.name, prefixes=prefixes, health_checks=health_checks)


@overload
def config_to_instance(config: PrefixConfiguration) -> Prefix:
def _sub_config_to_instance(config: PrefixConfiguration) -> Prefix:
...


@overload
def config_to_instance(config: HealthcheckConfiguration) -> Healthcheck:
def _sub_config_to_instance(config: HealthcheckConfiguration) -> Healthcheck:
...


def config_to_instance(
def _sub_config_to_instance(
config: PrefixConfiguration | HealthcheckConfiguration,
) -> Prefix | Healthcheck:
"""Convert a configuration to an instance of it's respective type."""
"""Convert a subconfiguration to an instance of it's respective type.
Convert subconfigurations to instances of the repsctive type
they are describing, e.g. a FRRPrefixConfiguration to a FRRoutingPrefix.
Args:
config: The subconfiguration to convert.
Returns:
An instance of the respective type the subconfiguration describes.
"""
match config:
case FRRPrefixConfiguration():
return FRRoutingPrefix(**config.model_dump(), executor=LocalExecutor())
Expand Down
2 changes: 1 addition & 1 deletion src/anycastd/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from anycastd.core._run import run_services
from anycastd.core._run import run_from_configuration
from anycastd.core._service import Service
12 changes: 10 additions & 2 deletions src/anycastd/core/_run.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import asyncio
from collections.abc import Iterable

from anycastd._configuration import MainConfiguration, config_to_service
from anycastd.core._service import Service


def run_services(services: Iterable[Service]) -> None:
def run_from_configuration(configuration: MainConfiguration) -> None:
"""Run anycastd using an instance of the main configuration."""
services = tuple(config_to_service(config) for config in configuration.services)
asyncio.run(run_services(services))


async def run_services(services: Iterable[Service]) -> None:
"""Run services.
Args:
services: The services to run.
"""
raise NotImplementedError
asyncio.gather(*(service.run() for service in services))
53 changes: 53 additions & 0 deletions tests/cli/test_main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from pathlib import Path
from unittest.mock import MagicMock

import anycastd
import pytest


def test_version_displayed_correctly(anycastd_cli):
Expand All @@ -9,3 +13,52 @@ def test_version_displayed_correctly(anycastd_cli):

assert result.exit_code == 0
assert result.stdout == expected


class TestRunCMD:
"""Test the run command."""

@pytest.fixture
def mock_configuration(self, mocker):
"""An autospecced mock instance of the MainConfiguration class."""
return mocker.create_autospec(
"anycastd._configuration.MainConfiguration", spec_set=True, instance=True
)

@pytest.fixture
def mock_run_from_configuration(self, mocker) -> MagicMock:
"""An autospecced mock of the run_from_configuration function."""
return mocker.patch("anycastd._cli.main.run_from_configuration", autospec=True)

@pytest.fixture
def mock_get_main_configuration(self, mocker, mock_configuration) -> MagicMock:
"""An autospecced mock of the _get_main_configuration function."""
mock = mocker.patch("anycastd._cli.main._get_main_configuration", autospec=True)
mock.return_value = mock_configuration
return mock

def test_calls_get_main_configuration_w_config_path(
self, anycastd_cli, mock_get_main_configuration, mock_run_from_configuration
):
"""_get_main_configuration is called with the config path."""
config = Path("/path/to/config.toml")

anycastd_cli("run", "--config", config.as_posix())

mock_get_main_configuration.assert_called_once_with(config)

def test_calls_run_from_configuration(
self,
anycastd_cli,
mock_configuration,
mock_run_from_configuration,
mock_get_main_configuration,
):
"""run_from_configuration is called.
Run from configuration is called with the main configuration object
obtained from the configuration file path.
"""
anycastd_cli("run")

mock_run_from_configuration.assert_called_once_with(mock_configuration)
4 changes: 2 additions & 2 deletions tests/configuration/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path

import pytest
from anycastd._configuration.conversion import config_to_instance
from anycastd._configuration.conversion import _sub_config_to_instance
from anycastd._configuration.healthcheck import (
CabourotteHealthcheckConfiguration,
HealthcheckConfiguration,
Expand Down Expand Up @@ -49,5 +49,5 @@ def test_subconfiguration_converted_to_instance(
expected: Prefix | Healthcheck,
):
"""A subconfiguration can be converted to an instance of the type it represents."""
converted = config_to_instance(config)
converted = _sub_config_to_instance(config)
assert converted == expected

0 comments on commit 4a727ca

Please sign in to comment.