diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/auth/api_keys.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/auth/api_keys.py index e70889e3de1..2609de81c5e 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/auth/api_keys.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/auth/api_keys.py @@ -26,7 +26,7 @@ async def create_api_key( product_name=product_name, api_key=api_key, ) - assert isinstance(result, ApiKeyGet) + assert isinstance(result, ApiKeyGet) # nosec return result @@ -45,7 +45,7 @@ async def get_api_key( product_name=product_name, api_key_id=api_key_id, ) - assert isinstance(result, ApiKeyGet) + assert isinstance(result, ApiKeyGet) # nosec return result @@ -63,4 +63,4 @@ async def delete_api_key( product_name=product_name, api_key_id=api_key_id, ) - assert result is None + assert result is None # nosec diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/__init__.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py new file mode 100644 index 00000000000..e212854bae5 --- /dev/null +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/licensed_items.py @@ -0,0 +1,104 @@ +import logging + +from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE +from models_library.api_schemas_webserver.licensed_items import ( + LicensedItemGet, + LicensedItemGetPage, +) +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.rabbitmq_basic_types import RPCMethodName +from models_library.resource_tracker import ServiceRunId +from models_library.users import UserID +from models_library.wallets import WalletID +from pydantic import TypeAdapter +from servicelib.logging_utils import log_decorator +from servicelib.rabbitmq import RabbitMQRPCClient + +_logger = logging.getLogger(__name__) + + +@log_decorator(_logger, level=logging.DEBUG) +async def get_licensed_items( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + product_name: str, + offset: int, + limit: int, +) -> LicensedItemGetPage: + result: LicensedItemGetPage = await rabbitmq_rpc_client.request( + WEBSERVER_RPC_NAMESPACE, + TypeAdapter(RPCMethodName).validate_python("get_licensed_items"), + product_name=product_name, + offset=offset, + limit=limit, + ) + assert isinstance(result, LicensedItemGetPage) + return result + + +@log_decorator(_logger, level=logging.DEBUG) +async def get_licensed_items_for_wallet( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + user_id: UserID, + product_name: ProductName, + wallet_id: WalletID, +) -> LicensedItemGet: + result: LicensedItemGet = await rabbitmq_rpc_client.request( + WEBSERVER_RPC_NAMESPACE, + TypeAdapter(RPCMethodName).validate_python("get_licensed_items_for_wallet"), + user_id=user_id, + product_name=product_name, + wallet_id=wallet_id, + ) + assert isinstance(result, LicensedItemGet) # nosec + return result + + +@log_decorator(_logger, level=logging.DEBUG) +async def checkout_licensed_item_for_wallet( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + user_id: UserID, + product_name: ProductName, + wallet_id: WalletID, + licensed_item_id: LicensedItemID, + num_of_seats: int, + service_run_id: ServiceRunId, +) -> None: + result = await rabbitmq_rpc_client.request( + WEBSERVER_RPC_NAMESPACE, + TypeAdapter(RPCMethodName).validate_python("checkout_licensed_item_for_wallet"), + user_id=user_id, + product_name=product_name, + wallet_id=wallet_id, + licensed_item_id=licensed_item_id, + num_of_seats=num_of_seats, + service_run_id=service_run_id, + ) + assert result is None # nosec + + +@log_decorator(_logger, level=logging.DEBUG) +async def release_licensed_item_for_wallet( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + user_id: UserID, + product_name: ProductName, + wallet_id: WalletID, + licensed_item_id: LicensedItemID, + num_of_seats: int, + service_run_id: ServiceRunId, +) -> None: + result = await rabbitmq_rpc_client.request( + WEBSERVER_RPC_NAMESPACE, + TypeAdapter(RPCMethodName).validate_python("release_licensed_item_for_wallet"), + user_id=user_id, + product_name=product_name, + wallet_id=wallet_id, + licensed_item_id=licensed_item_id, + num_of_seats=num_of_seats, + service_run_id=service_run_id, + ) + assert result is None # nosec diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py new file mode 100644 index 00000000000..fede0759b0d --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -0,0 +1,80 @@ +from aiohttp import web +from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE +from models_library.api_schemas_webserver.licensed_items import LicensedItemGetPage +from models_library.basic_types import IDStr +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import ServiceRunId +from models_library.rest_ordering import OrderBy +from models_library.users import UserID +from models_library.wallets import WalletID +from servicelib.rabbitmq import RPCRouter + +from ..rabbitmq import get_rabbitmq_rpc_server +from . import _licensed_items_api + +router = RPCRouter() + + +@router.expose() +async def get_licensed_items( + app: web.Application, + *, + product_name: ProductName, + offset: int, + limit: int, +) -> LicensedItemGetPage: + licensed_item_get_page: LicensedItemGetPage = ( + await _licensed_items_api.list_licensed_items( + app=app, + product_name=product_name, + offset=offset, + limit=limit, + order_by=OrderBy(field=IDStr("name")), + ) + ) + return licensed_item_get_page + + +@router.expose(reraise_if_error_type=(NotImplementedError,)) +async def get_licensed_items_for_wallet( + app: web.Application, + *, + user_id: UserID, + product_name: ProductName, + wallet_id: WalletID, +) -> None: + raise NotImplementedError + + +@router.expose(reraise_if_error_type=(NotImplementedError,)) +async def checkout_licensed_item_for_wallet( + app: web.Application, + *, + user_id: UserID, + product_name: ProductName, + wallet_id: WalletID, + licensed_item_id: LicensedItemID, + num_of_seats: int, + service_run_id: ServiceRunId, +) -> None: + raise NotImplementedError + + +@router.expose(reraise_if_error_type=(NotImplementedError,)) +async def release_licensed_item_for_wallet( + app: web.Application, + *, + user_id: str, + product_name: str, + wallet_id: WalletID, + licensed_item_id: LicensedItemID, + num_of_seats: int, + service_run_id: ServiceRunId, +) -> None: + raise NotImplementedError + + +async def register_rpc_routes_on_startup(app: web.Application): + rpc_server = get_rabbitmq_rpc_server(app) + await rpc_server.register_router(router, WEBSERVER_RPC_NAMESPACE, app) diff --git a/services/web/server/src/simcore_service_webserver/licenses/plugin.py b/services/web/server/src/simcore_service_webserver/licenses/plugin.py index 6c2ea7ce0d9..137c7b2d1dc 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/plugin.py +++ b/services/web/server/src/simcore_service_webserver/licenses/plugin.py @@ -7,7 +7,8 @@ from servicelib.aiohttp.application_keys import APP_SETTINGS_KEY from servicelib.aiohttp.application_setup import ModuleCategory, app_module_setup -from . import _licensed_items_handlers, _licensed_items_purchases_handlers +from ..rabbitmq import setup_rabbitmq +from . import _licensed_items_handlers, _licensed_items_purchases_handlers, _rpc _logger = logging.getLogger(__name__) @@ -25,3 +26,7 @@ def setup_licenses(app: web.Application): # routes app.router.add_routes(_licensed_items_handlers.routes) app.router.add_routes(_licensed_items_purchases_handlers.routes) + + setup_rabbitmq(app) + if app[APP_SETTINGS_KEY].WEBSERVER_RABBITMQ: + app.on_startup.append(_rpc.register_rpc_routes_on_startup) diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py new file mode 100644 index 00000000000..e3ab4f4cb3d --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -0,0 +1,127 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable + + +from collections.abc import Awaitable, Callable + +import pytest +from aiohttp.test_utils import TestClient +from models_library.licensed_items import LicensedResourceType +from models_library.products import ProductName +from pytest_mock import MockerFixture +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict +from pytest_simcore.helpers.typing_env import EnvVarsDict +from pytest_simcore.helpers.webserver_login import UserInfoDict +from servicelib.rabbitmq import RabbitMQRPCClient +from servicelib.rabbitmq.rpc_interfaces.webserver.licenses.licensed_items import ( + checkout_licensed_item_for_wallet, + get_licensed_items, + get_licensed_items_for_wallet, + release_licensed_item_for_wallet, +) +from settings_library.rabbit import RabbitSettings +from simcore_postgres_database.models.users import UserRole +from simcore_service_webserver.application_settings import ApplicationSettings +from simcore_service_webserver.licenses import _licensed_items_db + +pytest_simcore_core_services_selection = [ + "rabbit", +] + + +@pytest.fixture +def app_environment( + rabbit_service: RabbitSettings, + app_environment: EnvVarsDict, + monkeypatch: pytest.MonkeyPatch, +): + new_envs = setenvs_from_dict( + monkeypatch, + { + **app_environment, + "RABBIT_HOST": rabbit_service.RABBIT_HOST, + "RABBIT_PORT": f"{rabbit_service.RABBIT_PORT}", + "RABBIT_USER": rabbit_service.RABBIT_USER, + "RABBIT_SECURE": f"{rabbit_service.RABBIT_SECURE}", + "RABBIT_PASSWORD": rabbit_service.RABBIT_PASSWORD.get_secret_value(), + }, + ) + + settings = ApplicationSettings.create_from_envs() + assert settings.WEBSERVER_RABBITMQ + + return new_envs + + +@pytest.fixture +def user_role() -> UserRole: + return UserRole.USER + + +@pytest.fixture +async def rpc_client( + rabbitmq_rpc_client: Callable[[str], Awaitable[RabbitMQRPCClient]], + mocker: MockerFixture, +) -> RabbitMQRPCClient: + return await rabbitmq_rpc_client("client") + + +async def test_api_keys_workflow( + client: TestClient, + rpc_client: RabbitMQRPCClient, + osparc_product_name: ProductName, + logged_user: UserInfoDict, + pricing_plan_id: int, +): + assert client.app + + result = await get_licensed_items( + rpc_client, product_name=osparc_product_name, offset=0, limit=20 + ) + assert len(result.items) == 0 + assert result.total == 0 + + await _licensed_items_db.create( + client.app, + product_name=osparc_product_name, + name="Model A", + licensed_resource_type=LicensedResourceType.VIP_MODEL, + pricing_plan_id=pricing_plan_id, + ) + + result = await get_licensed_items( + rpc_client, product_name=osparc_product_name, offset=0, limit=20 + ) + assert len(result.items) == 1 + assert result.total == 1 + + with pytest.raises(NotImplementedError): + await get_licensed_items_for_wallet( + rpc_client, + user_id=logged_user["id"], + product_name=osparc_product_name, + wallet_id=1, + ) + + with pytest.raises(NotImplementedError): + await checkout_licensed_item_for_wallet( + rpc_client, + user_id=logged_user["id"], + product_name=osparc_product_name, + wallet_id=1, + licensed_item_id="c5139a2e-4e1f-4ebe-9bfd-d17f195111ee", + num_of_seats=1, + service_run_id="run_1", + ) + + with pytest.raises(NotImplementedError): + await release_licensed_item_for_wallet( + rpc_client, + user_id=logged_user["id"], + product_name=osparc_product_name, + wallet_id=1, + licensed_item_id="c5139a2e-4e1f-4ebe-9bfd-d17f195111ee", + num_of_seats=1, + service_run_id="run_1", + )