Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Select default wallet and pricing plan in the backend part 2 #4869

Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
76a9387
adding migration of projects_nodes
matusdrobuliak66 Oct 5, 2023
dda12c9
Merge branch 'master' into is922/store-selected-pricing-unit-with-the…
matusdrobuliak66 Oct 5, 2023
dfe6d0d
adding tests
matusdrobuliak66 Oct 7, 2023
1996507
Merge branch 'master' into is922/store-selected-pricing-unit-with-the…
matusdrobuliak66 Oct 7, 2023
165db8f
fix error
matusdrobuliak66 Oct 7, 2023
62b39c0
Merge branch 'master' into is922/store-selected-pricing-unit-with-the…
matusdrobuliak66 Oct 9, 2023
6731096
adding other endpoint
matusdrobuliak66 Oct 9, 2023
3c36b02
fix mypy
matusdrobuliak66 Oct 9, 2023
80d9a5d
Merge branch 'master' into is922/store-selected-pricing-unit-with-the…
matusdrobuliak66 Oct 9, 2023
a905b1b
adding open-api specs
matusdrobuliak66 Oct 9, 2023
ebbd41b
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 9, 2023
1eaa817
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 10, 2023
a6f4563
moving selection of default wallet to the backend
matusdrobuliak66 Oct 11, 2023
746bca0
fix
matusdrobuliak66 Oct 11, 2023
e415ec6
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 11, 2023
849fbe0
adds free creates
pcrespov Oct 10, 2023
a7935a9
services/invitations version: 1.0.2 → 1.1.0
pcrespov Oct 10, 2023
75fa67d
rename as extra credits
pcrespov Oct 10, 2023
3e17ce3
rename in invtations service
pcrespov Oct 10, 2023
9e2dd7f
web api
pcrespov Oct 10, 2023
951d496
setup
pcrespov Oct 10, 2023
4306554
test
pcrespov Oct 10, 2023
a91c91b
connected SIGNAL_ON_USER_CONFIRMATION, adds data and disabled case
pcrespov Oct 10, 2023
c35e159
fixes after merge
pcrespov Oct 10, 2023
db31aa8
fixes mypy
pcrespov Oct 10, 2023
8f4d370
services/webserver api version: 0.32.0 → 0.33.0
pcrespov Oct 10, 2023
6edc985
fixes tests
pcrespov Oct 10, 2023
8e2830f
fix test and cleanup
pcrespov Oct 11, 2023
f0ebaae
fixes test
pcrespov Oct 11, 2023
3f14402
minor
pcrespov Oct 11, 2023
f0fea53
merge 'optional extra_credits in invitations' pr
matusdrobuliak66 Oct 11, 2023
143fa7a
fix after merge conflict
matusdrobuliak66 Oct 11, 2023
604333a
using already created is payment enabled field
matusdrobuliak66 Oct 11, 2023
106c6fe
merge master
matusdrobuliak66 Oct 13, 2023
ecf96dd
final changes
matusdrobuliak66 Oct 13, 2023
36b53f5
removing comment
matusdrobuliak66 Oct 13, 2023
4005c2f
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 16, 2023
e03e568
adding default pricing plan to dynamic services
matusdrobuliak66 Oct 16, 2023
45496ba
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 16, 2023
76ef844
fix test
matusdrobuliak66 Oct 16, 2023
921eb8d
Merge branches 'is922/select-default-wallet-and-pricing-plan-in-the-b…
matusdrobuliak66 Oct 16, 2023
15ff075
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 16, 2023
d56c4c8
Merge branch 'is922/select-default-wallet-and-pricing-plan-in-the-bac…
matusdrobuliak66 Oct 16, 2023
8db90f2
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 17, 2023
a267083
migration and fix
matusdrobuliak66 Oct 17, 2023
33fd5e1
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 17, 2023
ad7921f
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 17, 2023
5b32ac0
@GitHK @pcrespov @sanderegg review
matusdrobuliak66 Oct 17, 2023
155bb9e
Merge branch 'master' into is922/select-default-wallet-and-pricing-pl…
matusdrobuliak66 Oct 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Any, ClassVar, TypeAlias

from models_library.resource_tracker import HardwareInfo, PricingInfo
from pydantic import BaseModel, ByteSize, Field

from ..services import ServicePortKey
Expand Down Expand Up @@ -46,6 +47,14 @@ class DynamicServiceCreate(ServiceDetails):
default=None,
description="contains information about the wallet used to bill the running service",
)
pricing_info: PricingInfo | None = Field(
default=None,
description="contains pricing information (ex. pricing plan and unit ids)",
)
hardware_info: HardwareInfo | None = Field(
default=None,
description="contains harware information (ex. aws_ec2_instances)",
)

class Config:
schema_extra: ClassVar[dict[str, Any]] = {
Expand All @@ -62,6 +71,8 @@ class Config:
"examples"
][0],
"wallet_info": WalletInfo.Config.schema_extra["examples"][0],
"pricing_info": PricingInfo.Config.schema_extra["examples"][0],
"hardware_info": HardwareInfo.Config.schema_extra["examples"][0],
}
}

Expand Down
26 changes: 24 additions & 2 deletions packages/models-library/src/models_library/resource_tracker.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from enum import auto
from typing import TypeAlias
from typing import Any, ClassVar, TypeAlias

from pydantic import PositiveInt
from pydantic import BaseModel, PositiveInt

from .utils.enums import StrAutoEnum

Expand Down Expand Up @@ -37,3 +37,25 @@ class CreditClassification(StrAutoEnum):

class PricingPlanClassification(StrAutoEnum):
TIER = auto()


class PricingInfo(BaseModel):
pricing_plan_id: PricingPlanId | None
pricing_unit_id: PricingUnitId | None
pricing_unit_cost_id: PricingUnitCostId | None

class Config:
schema_extra: ClassVar[dict[str, Any]] = {
"examples": [
{"pricing_plan_id": 1, "pricing_unit_id": 1, "pricing_unit_cost_id": 1}
]
}


class HardwareInfo(BaseModel):
aws_ec2_instances: list[str]

class Config:
schema_extra: ClassVar[dict[str, Any]] = {
"examples": [{"aws_ec2_instances": ["c6a.4xlarge"]}]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""migration of aws_ec2_instances data in pricing units

Revision ID: 5c62b190e124
Revises: 7777d181dc1f
Create Date: 2023-10-17 05:15:29.780925+00:00

"""
from alembic import op
from simcore_postgres_database.models.resource_tracker_pricing_units import (
resource_tracker_pricing_units,
)

# revision identifiers, used by Alembic.
revision = "5c62b190e124"
down_revision = "7777d181dc1f"
branch_labels = None
depends_on = None


def upgrade():
# One time migration to populate specific info with some reasonable value, it will be changed manually based on concrete needs
op.execute(
resource_tracker_pricing_units.update().values(
specific_info={"aws_ec2_instances": ["t3.medium"]}
)
)


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from models_library.callbacks_mapping import CallbacksMapping
from models_library.generated_models.docker_rest_api import ContainerState, Status2
from models_library.projects_nodes_io import NodeID
from models_library.resource_tracker import HardwareInfo, PricingInfo
from models_library.service_settings_labels import (
DynamicSidecarServiceLabels,
PathMappingsLabel,
Expand Down Expand Up @@ -427,6 +428,14 @@ def endpoint(self) -> AnyHttpUrl:
default=None,
description="contains information about the wallet used to bill the running service",
)
pricing_info: PricingInfo | None = Field(
default=None,
description="contains pricing information so we know what is the cost of running of the service",
)
hardware_info: HardwareInfo | None = Field(
default=None,
description="contains harware information so we know on which hardware to run the service",
)

@property
def get_proxy_endpoint(self) -> AnyHttpUrl:
Expand Down Expand Up @@ -485,6 +494,8 @@ def from_http_request(
"request_simcore_user_agent": request_simcore_user_agent,
"dynamic_sidecar": {"service_removal_state": {"can_save": can_save}},
"wallet_info": service.wallet_info,
"pricing_info": service.pricing_info,
"hardware_info": service.hardware_info,
}
if run_id:
obj_dict["run_id"] = run_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

from .....core.settings import DynamicSidecarSettings
from .....models.dynamic_services_scheduler import SchedulerData
from .....modules.resource_usage_client import ResourceUsageApi
from .....utils.db import get_repository
from ....db.repositories.groups_extra_properties import GroupsExtraPropertiesRepository
from ....db.repositories.projects import ProjectsRepository
Expand Down Expand Up @@ -132,14 +131,9 @@ async def progress_create_containers(
if scheduler_data.wallet_info:
wallet_id = scheduler_data.wallet_info.wallet_id
wallet_name = scheduler_data.wallet_info.wallet_name
resource_usage_api = ResourceUsageApi.get_from_state(app)
(
pricing_plan_id,
pricing_unit_id,
pricing_unit_cost_id,
) = await resource_usage_api.get_default_service_pricing_plan_and_pricing_unit(
scheduler_data.product_name, scheduler_data.key, scheduler_data.version
)
pricing_plan_id = scheduler_data.pricing_info.pricing_plan_id
pricing_unit_id = scheduler_data.pricing_info.pricing_unit_id
pricing_unit_cost_id = scheduler_data.pricing_info.pricing_unit_cost_id

metrics_params = CreateServiceMetricsAdditionalParams(
wallet_id=wallet_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from models_library.aiodocker_api import AioDockerServiceSpec
from models_library.callbacks_mapping import CallbacksMapping
from models_library.docker import to_simcore_runtime_docker_label_key
from models_library.resource_tracker import HardwareInfo, PricingInfo
from models_library.service_settings_labels import (
SimcoreServiceLabels,
SimcoreServiceSettingsLabel,
Expand Down Expand Up @@ -153,6 +154,8 @@ def expected_dynamic_sidecar_spec(
"request_simcore_user_agent": request_simcore_user_agent,
"restart_policy": "on-inputs-downloaded",
"wallet_info": WalletInfo.Config.schema_extra["examples"][0],
"pricing_info": PricingInfo.Config.schema_extra["examples"][0],
"hardware_info": HardwareInfo.Config.schema_extra["examples"][0],
"service_name": "dy-sidecar_75c7f3f4-18f9-4678-8610-54a2ade78eaa",
"service_port": 65534,
"service_resources": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PricingUnitCostsDB(BaseModel):
valid_from: datetime
valid_to: datetime | None
created: datetime
comment: str
comment: str | None
modified: datetime

class Config:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,13 @@ async def get_service_pricing_plan(request: Request):
ctx = CatalogRequestContext.create(request)
path_params = parse_request_path_parameters_as(ServicePathParams, request)

service_pricing_plan: ServicePricingPlanGet = (
await get_default_service_pricing_plan(
app=request.app,
product_name=ctx.product_name,
service_key=path_params.service_key,
service_version=path_params.service_version,
)
service_pricing_plan = await get_default_service_pricing_plan(
app=request.app,
product_name=ctx.product_name,
service_key=path_params.service_key,
service_version=path_params.service_version,
)

return envelope_json_response(service_pricing_plan)
return envelope_json_response(
parse_obj_as(ServicePricingPlanGet, service_pricing_plan)
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from models_library.projects import ProjectID
from models_library.projects_nodes_io import NodeIDStr
from models_library.rabbitmq_messages import ProgressRabbitMessageProject, ProgressType
from models_library.resource_tracker import HardwareInfo, PricingInfo
from models_library.services import ServicePortKey
from models_library.services_resources import (
ServiceResourcesDict,
Expand Down Expand Up @@ -96,6 +97,8 @@ async def run_dynamic_service(
simcore_user_agent: str,
service_resources: ServiceResourcesDict,
wallet_info: WalletInfo | None,
pricing_info: PricingInfo | None,
hardware_info: HardwareInfo | None,
) -> DataType:
"""
Requests to run (i.e. create and start) a dynamic service:
Expand All @@ -115,6 +118,8 @@ async def run_dynamic_service(
service_resources
),
"wallet_info": wallet_info,
"pricing_info": pricing_info,
"hardware_info": hardware_info,
}

headers = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
GroupExtraPropertiesRepo,
)
from simcore_service_webserver.db.plugin import get_database_engine
from simcore_service_webserver.users.exceptions import UserDefaultWalletNotFoundError

from .._constants import RQ_PRODUCT_KEY
from .._meta import API_VTAG as VTAG
Expand Down Expand Up @@ -114,9 +115,7 @@ async def start_computation(request: web.Request) -> web.Response:
preference_class=user_preferences_api.PreferredWalletIdFrontendUserPreference,
)
if user_default_wallet_preference is None:
raise ValueError(
"User does not have default wallet - this should not happen"
)
raise UserDefaultWalletNotFoundError(uid=req_ctx.user_id)
project_wallet_id = parse_obj_as(
WalletID, user_default_wallet_preference.value
)
Expand Down Expand Up @@ -198,6 +197,8 @@ async def start_computation(request: web.Request) -> web.Response:
reason=exc.reason,
http_error_cls=get_http_error(exc.status) or web.HTTPServiceUnavailable,
)
except UserDefaultWalletNotFoundError as exc:
return create_error_response(exc, http_error_cls=web.HTTPNotFound)


@routes.post(f"/{VTAG}/computations/{{project_id}}:stop", name="stop_computation")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from ._nodes_api import NodeScreenshot, get_node_screenshots
from .db import ProjectDBAPI
from .exceptions import (
DefaultPricingUnitNotFoundError,
NodeNotFoundError,
ProjectNodeResourcesInsufficientRightsError,
ProjectNodeResourcesInvalidError,
Expand All @@ -79,6 +80,7 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
ProjectNotFoundError,
NodeNotFoundError,
UserDefaultWalletNotFoundError,
DefaultPricingUnitNotFoundError,
) as exc:
raise web.HTTPNotFound(reason=f"{exc}") from exc

Expand Down Expand Up @@ -173,9 +175,9 @@ async def get_node(request: web.Request) -> web.Response:

if "data" not in service_data:
# dynamic-service NODE STATE
assert (
assert ( # nosec
parse_obj_as(NodeGet | NodeGetIdle, service_data) is not None
) # nosec
)
return envelope_json_response(service_data)

# LEGACY-service NODE STATE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging

from aiohttp import web
from models_library.api_schemas_webserver.resource_usage import PricingUnitGet
from models_library.projects import ProjectID
from models_library.projects_nodes_io import NodeID
from models_library.resource_tracker import PricingPlanId, PricingUnitId
Expand Down Expand Up @@ -83,7 +84,14 @@ async def get_project_node_pricing_unit(request: web.Request):
pricing_unit_get = await rut_api.get_pricing_plan_unit(
request.app, req_ctx.product_name, pricing_plan_id, pricing_unit_id
)
return envelope_json_response(pricing_unit_get)
webserver_pricing_unit_get = PricingUnitGet(
pricing_unit_id=pricing_unit_get.pricing_unit_id,
unit_name=pricing_unit_get.unit_name,
unit_extra_info=pricing_unit_get.unit_extra_info,
current_cost_per_unit=pricing_unit_get.current_cost_per_unit,
default=pricing_unit_get.default,
)
return envelope_json_response(webserver_pricing_unit_get)


class _ProjectNodePricingUnitPathParams(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,14 @@ class ProjectNodeResourcesInvalidError(BaseProjectError):

class ProjectNodeResourcesInsufficientRightsError(BaseProjectError):
...


class DefaultPricingUnitNotFoundError(BaseProjectError):
"""Node was not found in project"""

def __init__(self, project_uuid: str, node_uuid: str):
super().__init__(
f"Default pricing unit not found for node {node_uuid} in project {project_uuid}"
)
self.node_uuid = node_uuid
self.project_uuid = project_uuid
Loading