Skip to content

Commit e94782d

Browse files
✨ introduce webserver rpc endpoints for licenses (#6946)
1 parent ff6f85a commit e94782d

File tree

6 files changed

+320
-4
lines changed

6 files changed

+320
-4
lines changed

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/auth/api_keys.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async def create_api_key(
2626
product_name=product_name,
2727
api_key=api_key,
2828
)
29-
assert isinstance(result, ApiKeyGet)
29+
assert isinstance(result, ApiKeyGet) # nosec
3030
return result
3131

3232

@@ -45,7 +45,7 @@ async def get_api_key(
4545
product_name=product_name,
4646
api_key_id=api_key_id,
4747
)
48-
assert isinstance(result, ApiKeyGet)
48+
assert isinstance(result, ApiKeyGet) # nosec
4949
return result
5050

5151

@@ -63,4 +63,4 @@ async def delete_api_key(
6363
product_name=product_name,
6464
api_key_id=api_key_id,
6565
)
66-
assert result is None
66+
assert result is None # nosec

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/licenses/__init__.py

Whitespace-only changes.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import logging
2+
3+
from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE
4+
from models_library.api_schemas_webserver.licensed_items import (
5+
LicensedItemGet,
6+
LicensedItemGetPage,
7+
)
8+
from models_library.licensed_items import LicensedItemID
9+
from models_library.products import ProductName
10+
from models_library.rabbitmq_basic_types import RPCMethodName
11+
from models_library.resource_tracker import ServiceRunId
12+
from models_library.users import UserID
13+
from models_library.wallets import WalletID
14+
from pydantic import TypeAdapter
15+
from servicelib.logging_utils import log_decorator
16+
from servicelib.rabbitmq import RabbitMQRPCClient
17+
18+
_logger = logging.getLogger(__name__)
19+
20+
21+
@log_decorator(_logger, level=logging.DEBUG)
22+
async def get_licensed_items(
23+
rabbitmq_rpc_client: RabbitMQRPCClient,
24+
*,
25+
product_name: str,
26+
offset: int,
27+
limit: int,
28+
) -> LicensedItemGetPage:
29+
result: LicensedItemGetPage = await rabbitmq_rpc_client.request(
30+
WEBSERVER_RPC_NAMESPACE,
31+
TypeAdapter(RPCMethodName).validate_python("get_licensed_items"),
32+
product_name=product_name,
33+
offset=offset,
34+
limit=limit,
35+
)
36+
assert isinstance(result, LicensedItemGetPage)
37+
return result
38+
39+
40+
@log_decorator(_logger, level=logging.DEBUG)
41+
async def get_licensed_items_for_wallet(
42+
rabbitmq_rpc_client: RabbitMQRPCClient,
43+
*,
44+
user_id: UserID,
45+
product_name: ProductName,
46+
wallet_id: WalletID,
47+
) -> LicensedItemGet:
48+
result: LicensedItemGet = await rabbitmq_rpc_client.request(
49+
WEBSERVER_RPC_NAMESPACE,
50+
TypeAdapter(RPCMethodName).validate_python("get_licensed_items_for_wallet"),
51+
user_id=user_id,
52+
product_name=product_name,
53+
wallet_id=wallet_id,
54+
)
55+
assert isinstance(result, LicensedItemGet) # nosec
56+
return result
57+
58+
59+
@log_decorator(_logger, level=logging.DEBUG)
60+
async def checkout_licensed_item_for_wallet(
61+
rabbitmq_rpc_client: RabbitMQRPCClient,
62+
*,
63+
user_id: UserID,
64+
product_name: ProductName,
65+
wallet_id: WalletID,
66+
licensed_item_id: LicensedItemID,
67+
num_of_seats: int,
68+
service_run_id: ServiceRunId,
69+
) -> None:
70+
result = await rabbitmq_rpc_client.request(
71+
WEBSERVER_RPC_NAMESPACE,
72+
TypeAdapter(RPCMethodName).validate_python("checkout_licensed_item_for_wallet"),
73+
user_id=user_id,
74+
product_name=product_name,
75+
wallet_id=wallet_id,
76+
licensed_item_id=licensed_item_id,
77+
num_of_seats=num_of_seats,
78+
service_run_id=service_run_id,
79+
)
80+
assert result is None # nosec
81+
82+
83+
@log_decorator(_logger, level=logging.DEBUG)
84+
async def release_licensed_item_for_wallet(
85+
rabbitmq_rpc_client: RabbitMQRPCClient,
86+
*,
87+
user_id: UserID,
88+
product_name: ProductName,
89+
wallet_id: WalletID,
90+
licensed_item_id: LicensedItemID,
91+
num_of_seats: int,
92+
service_run_id: ServiceRunId,
93+
) -> None:
94+
result = await rabbitmq_rpc_client.request(
95+
WEBSERVER_RPC_NAMESPACE,
96+
TypeAdapter(RPCMethodName).validate_python("release_licensed_item_for_wallet"),
97+
user_id=user_id,
98+
product_name=product_name,
99+
wallet_id=wallet_id,
100+
licensed_item_id=licensed_item_id,
101+
num_of_seats=num_of_seats,
102+
service_run_id=service_run_id,
103+
)
104+
assert result is None # nosec
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from aiohttp import web
2+
from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE
3+
from models_library.api_schemas_webserver.licensed_items import LicensedItemGetPage
4+
from models_library.basic_types import IDStr
5+
from models_library.licensed_items import LicensedItemID
6+
from models_library.products import ProductName
7+
from models_library.resource_tracker import ServiceRunId
8+
from models_library.rest_ordering import OrderBy
9+
from models_library.users import UserID
10+
from models_library.wallets import WalletID
11+
from servicelib.rabbitmq import RPCRouter
12+
13+
from ..rabbitmq import get_rabbitmq_rpc_server
14+
from . import _licensed_items_api
15+
16+
router = RPCRouter()
17+
18+
19+
@router.expose()
20+
async def get_licensed_items(
21+
app: web.Application,
22+
*,
23+
product_name: ProductName,
24+
offset: int,
25+
limit: int,
26+
) -> LicensedItemGetPage:
27+
licensed_item_get_page: LicensedItemGetPage = (
28+
await _licensed_items_api.list_licensed_items(
29+
app=app,
30+
product_name=product_name,
31+
offset=offset,
32+
limit=limit,
33+
order_by=OrderBy(field=IDStr("name")),
34+
)
35+
)
36+
return licensed_item_get_page
37+
38+
39+
@router.expose(reraise_if_error_type=(NotImplementedError,))
40+
async def get_licensed_items_for_wallet(
41+
app: web.Application,
42+
*,
43+
user_id: UserID,
44+
product_name: ProductName,
45+
wallet_id: WalletID,
46+
) -> None:
47+
raise NotImplementedError
48+
49+
50+
@router.expose(reraise_if_error_type=(NotImplementedError,))
51+
async def checkout_licensed_item_for_wallet(
52+
app: web.Application,
53+
*,
54+
user_id: UserID,
55+
product_name: ProductName,
56+
wallet_id: WalletID,
57+
licensed_item_id: LicensedItemID,
58+
num_of_seats: int,
59+
service_run_id: ServiceRunId,
60+
) -> None:
61+
raise NotImplementedError
62+
63+
64+
@router.expose(reraise_if_error_type=(NotImplementedError,))
65+
async def release_licensed_item_for_wallet(
66+
app: web.Application,
67+
*,
68+
user_id: str,
69+
product_name: str,
70+
wallet_id: WalletID,
71+
licensed_item_id: LicensedItemID,
72+
num_of_seats: int,
73+
service_run_id: ServiceRunId,
74+
) -> None:
75+
raise NotImplementedError
76+
77+
78+
async def register_rpc_routes_on_startup(app: web.Application):
79+
rpc_server = get_rabbitmq_rpc_server(app)
80+
await rpc_server.register_router(router, WEBSERVER_RPC_NAMESPACE, app)

services/web/server/src/simcore_service_webserver/licenses/plugin.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from servicelib.aiohttp.application_keys import APP_SETTINGS_KEY
88
from servicelib.aiohttp.application_setup import ModuleCategory, app_module_setup
99

10-
from . import _licensed_items_handlers, _licensed_items_purchases_handlers
10+
from ..rabbitmq import setup_rabbitmq
11+
from . import _licensed_items_handlers, _licensed_items_purchases_handlers, _rpc
1112

1213
_logger = logging.getLogger(__name__)
1314

@@ -25,3 +26,7 @@ def setup_licenses(app: web.Application):
2526
# routes
2627
app.router.add_routes(_licensed_items_handlers.routes)
2728
app.router.add_routes(_licensed_items_purchases_handlers.routes)
29+
30+
setup_rabbitmq(app)
31+
if app[APP_SETTINGS_KEY].WEBSERVER_RABBITMQ:
32+
app.on_startup.append(_rpc.register_rpc_routes_on_startup)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
5+
6+
from collections.abc import Awaitable, Callable
7+
8+
import pytest
9+
from aiohttp.test_utils import TestClient
10+
from models_library.licensed_items import LicensedResourceType
11+
from models_library.products import ProductName
12+
from pytest_mock import MockerFixture
13+
from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict
14+
from pytest_simcore.helpers.typing_env import EnvVarsDict
15+
from pytest_simcore.helpers.webserver_login import UserInfoDict
16+
from servicelib.rabbitmq import RabbitMQRPCClient
17+
from servicelib.rabbitmq.rpc_interfaces.webserver.licenses.licensed_items import (
18+
checkout_licensed_item_for_wallet,
19+
get_licensed_items,
20+
get_licensed_items_for_wallet,
21+
release_licensed_item_for_wallet,
22+
)
23+
from settings_library.rabbit import RabbitSettings
24+
from simcore_postgres_database.models.users import UserRole
25+
from simcore_service_webserver.application_settings import ApplicationSettings
26+
from simcore_service_webserver.licenses import _licensed_items_db
27+
28+
pytest_simcore_core_services_selection = [
29+
"rabbit",
30+
]
31+
32+
33+
@pytest.fixture
34+
def app_environment(
35+
rabbit_service: RabbitSettings,
36+
app_environment: EnvVarsDict,
37+
monkeypatch: pytest.MonkeyPatch,
38+
):
39+
new_envs = setenvs_from_dict(
40+
monkeypatch,
41+
{
42+
**app_environment,
43+
"RABBIT_HOST": rabbit_service.RABBIT_HOST,
44+
"RABBIT_PORT": f"{rabbit_service.RABBIT_PORT}",
45+
"RABBIT_USER": rabbit_service.RABBIT_USER,
46+
"RABBIT_SECURE": f"{rabbit_service.RABBIT_SECURE}",
47+
"RABBIT_PASSWORD": rabbit_service.RABBIT_PASSWORD.get_secret_value(),
48+
},
49+
)
50+
51+
settings = ApplicationSettings.create_from_envs()
52+
assert settings.WEBSERVER_RABBITMQ
53+
54+
return new_envs
55+
56+
57+
@pytest.fixture
58+
def user_role() -> UserRole:
59+
return UserRole.USER
60+
61+
62+
@pytest.fixture
63+
async def rpc_client(
64+
rabbitmq_rpc_client: Callable[[str], Awaitable[RabbitMQRPCClient]],
65+
mocker: MockerFixture,
66+
) -> RabbitMQRPCClient:
67+
return await rabbitmq_rpc_client("client")
68+
69+
70+
async def test_api_keys_workflow(
71+
client: TestClient,
72+
rpc_client: RabbitMQRPCClient,
73+
osparc_product_name: ProductName,
74+
logged_user: UserInfoDict,
75+
pricing_plan_id: int,
76+
):
77+
assert client.app
78+
79+
result = await get_licensed_items(
80+
rpc_client, product_name=osparc_product_name, offset=0, limit=20
81+
)
82+
assert len(result.items) == 0
83+
assert result.total == 0
84+
85+
await _licensed_items_db.create(
86+
client.app,
87+
product_name=osparc_product_name,
88+
name="Model A",
89+
licensed_resource_type=LicensedResourceType.VIP_MODEL,
90+
pricing_plan_id=pricing_plan_id,
91+
)
92+
93+
result = await get_licensed_items(
94+
rpc_client, product_name=osparc_product_name, offset=0, limit=20
95+
)
96+
assert len(result.items) == 1
97+
assert result.total == 1
98+
99+
with pytest.raises(NotImplementedError):
100+
await get_licensed_items_for_wallet(
101+
rpc_client,
102+
user_id=logged_user["id"],
103+
product_name=osparc_product_name,
104+
wallet_id=1,
105+
)
106+
107+
with pytest.raises(NotImplementedError):
108+
await checkout_licensed_item_for_wallet(
109+
rpc_client,
110+
user_id=logged_user["id"],
111+
product_name=osparc_product_name,
112+
wallet_id=1,
113+
licensed_item_id="c5139a2e-4e1f-4ebe-9bfd-d17f195111ee",
114+
num_of_seats=1,
115+
service_run_id="run_1",
116+
)
117+
118+
with pytest.raises(NotImplementedError):
119+
await release_licensed_item_for_wallet(
120+
rpc_client,
121+
user_id=logged_user["id"],
122+
product_name=osparc_product_name,
123+
wallet_id=1,
124+
licensed_item_id="c5139a2e-4e1f-4ebe-9bfd-d17f195111ee",
125+
num_of_seats=1,
126+
service_run_id="run_1",
127+
)

0 commit comments

Comments
 (0)