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

πŸ› fix bug pricing plan listing + introduce unit_specific_info field (πŸ—ƒοΈ) #4839

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
Expand Up @@ -14,6 +14,7 @@
class PricingUnitGet(BaseModel):
pricing_unit_id: PricingUnitId
unit_name: str
unit_attributes: dict
current_cost_per_unit: Decimal
current_cost_per_unit_id: PricingUnitCostId
default: bool
Expand All @@ -25,6 +26,7 @@ class Config:
{
"pricing_unit_id": 1,
"unit_name": "SMALL",
"unit_attributes": {},
"current_cost_per_unit": 5.7,
"current_cost_per_unit_id": 1,
"default": True,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ServiceRunGet(
class PricingUnitGet(OutputSchema):
pricing_unit_id: PricingUnitId
unit_name: str
unit_attributes: dict
current_cost_per_unit: Decimal
default: bool

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""refactor pricing units table

Revision ID: f613247f5bb1
Revises: b102946c8134
Create Date: 2023-10-07 15:13:38.557368+00:00

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "f613247f5bb1"
down_revision = "b102946c8134"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("resource_tracker_pricing_unit_costs", "specific_info")
op.add_column(
"resource_tracker_pricing_units",
sa.Column(
"unit_attributes",
postgresql.JSONB(astext_type=sa.Text()),
server_default=sa.text("'{}'::jsonb"),
nullable=False,
),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("resource_tracker_pricing_units", "unit_attributes")
op.add_column(
"resource_tracker_pricing_unit_costs",
sa.Column(
"specific_info",
postgresql.JSONB(astext_type=sa.Text()),
autoincrement=False,
nullable=False,
),
)
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
history and do not update the rows of this table.
"""
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import JSONB

from ._common import NUMERIC_KWARGS, column_created_datetime, column_modified_datetime
from .base import metadata
Expand Down Expand Up @@ -65,13 +64,6 @@
doc="To when the pricing unit was active, if null it is still active",
index=True,
),
sa.Column(
"specific_info",
JSONB,
nullable=False,
default="'{}'::jsonb",
doc="Specific internal info of the pricing unit, ex. for tiers we can store in which EC2 instance type we run the service.",
),
column_created_datetime(timezone=True),
sa.Column(
"comment",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
nullable=False,
doc="The custom name of the pricing plan, ex. SMALL, MEDIUM, LARGE",
),
sa.Column(
"unit_attributes",
JSONB,
nullable=False,
default="'{}'::jsonb",
doc="Additional public information about pricing unit, ex. more detail description or how many CPUs there are.",
),
sa.Column(
"default",
sa.Boolean(),
Expand Down
5 changes: 5 additions & 0 deletions services/resource-usage-tracker/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@
"required": [
"pricing_unit_id",
"unit_name",
"unit_attributes",
"current_cost_per_unit",
"current_cost_per_unit_id",
"default",
Expand All @@ -557,6 +558,10 @@
"title": "Unit Name",
"type": "string"
},
"unit_attributes": {
"title": "Unit Attributes",
"type": "object"
},
"current_cost_per_unit": {
"title": "Current Cost Per Unit",
"type": "number"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
from fastapi import FastAPI
from fastapi_pagination import add_pagination as setup_fastapi_pagination
from servicelib.fastapi.openapi import override_fastapi_openapi_method
from simcore_service_resource_usage_tracker.core.errors import (
MyHTTPException,
my_http_exception_handler,
)

from .._meta import (
API_VERSION,
Expand Down Expand Up @@ -45,7 +49,7 @@ def create_app(settings: ApplicationSettings) -> FastAPI:
setup_fastapi_pagination(app)

# ERROR HANDLERS
# ... add here ...
app.add_exception_handler(MyHTTPException, my_http_exception_handler)

if settings.RESOURCE_USAGE_TRACKER_POSTGRES:
setup_db(app)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic.errors import PydanticErrorMixin


Expand All @@ -9,13 +11,11 @@ class ConfigurationError(ResourceUsageTrackerRuntimeError):
msg_template: str = "Application misconfiguration: {msg}"


class CreateServiceRunError(ResourceUsageTrackerRuntimeError):
msg_template: str = "Error during creation of new service run record in DB: {msg}"
class MyHTTPException(HTTPException):
pass


class CreateTransactionError(ResourceUsageTrackerRuntimeError):
msg_template: str = "Error during creation of new transaction record in DB: {msg}"


class ResourceUsageTrackerCustomRuntimeError(ResourceUsageTrackerRuntimeError):
msg_template: str = "Error: {msg}"
def my_http_exception_handler(
request: Request, exc: HTTPException # pylint: disable=unused-argument
) -> JSONResponse:
return JSONResponse(status_code=exc.status_code, content={"message": exc.detail})
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class PricingUnitCostsDB(BaseModel):
cost_per_unit: Decimal
valid_from: datetime
valid_to: datetime | None
specific_info: dict
created: datetime
comment: str
modified: datetime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PricingUnitsDB(BaseModel):
pricing_unit_id: PricingUnitId
pricing_plan_id: PricingPlanId
unit_name: str
unit_attributes: dict
default: bool
specific_info: dict
created: datetime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@
)
from sqlalchemy.dialects.postgresql import ARRAY, INTEGER

from ....core.errors import (
CreateServiceRunError,
CreateTransactionError,
ResourceUsageTrackerCustomRuntimeError,
)
from ....core.errors import MyHTTPException
from ....models.resource_tracker_credit_transactions import (
CreditTransactionCreate,
CreditTransactionCreditsAndStatusUpdate,
Expand Down Expand Up @@ -110,7 +106,9 @@ async def create_service_run(self, data: ServiceRunCreate) -> ServiceRunId:
result = await conn.execute(insert_stmt)
row = result.first()
if row is None:
raise CreateServiceRunError(msg=f"Service was not created: {data}")
raise MyHTTPException(
status_code=404, detail=f"Service was not created: {data}"
)
return row[0]

async def update_service_run_last_heartbeat(
Expand Down Expand Up @@ -292,7 +290,9 @@ async def create_credit_transaction(
result = await conn.execute(insert_stmt)
row = result.first()
if row is None:
raise CreateTransactionError(msg=f"Transaction was not created: {data}")
raise MyHTTPException(
status_code=404, detail=f"Transaction was not created: {data}"
)
return row[0]

async def update_credit_transaction_credits(
Expand Down Expand Up @@ -399,12 +399,22 @@ def _version(column_or_value):
return sa.func.string_to_array(column_or_value, ".").cast(ARRAY(INTEGER))

async with self.db_engine.begin() as conn:
query = sa.select(
resource_tracker_pricing_plan_to_service.c.service_key,
resource_tracker_pricing_plan_to_service.c.service_version,
)
# Firstly find the correct service version
query = (
query.where(
sa.select(
resource_tracker_pricing_plan_to_service.c.service_key,
resource_tracker_pricing_plan_to_service.c.service_version,
)
.select_from(
resource_tracker_pricing_plan_to_service.join(
resource_tracker_pricing_plans,
(
resource_tracker_pricing_plan_to_service.c.pricing_plan_id
== resource_tracker_pricing_plans.c.pricing_plan_id
),
)
)
.where(
(
_version(
resource_tracker_pricing_plan_to_service.c.service_version
Expand All @@ -425,36 +435,49 @@ def _version(column_or_value):
)
.limit(1)
)

result = await conn.execute(query)
row = result.first()
if row is None:
return []
latest_service_key, latest_service_version = row

query = sa.select(
resource_tracker_pricing_plans.c.pricing_plan_id,
resource_tracker_pricing_plans.c.display_name,
resource_tracker_pricing_plans.c.description,
resource_tracker_pricing_plans.c.classification,
resource_tracker_pricing_plans.c.is_active,
resource_tracker_pricing_plans.c.created,
resource_tracker_pricing_plans.c.pricing_plan_key,
resource_tracker_pricing_plan_to_service.c.service_default_plan,
)
query = query.where(
(
_version(resource_tracker_pricing_plan_to_service.c.service_version)
== _version(latest_service_version)
# Now choose all pricing plans connected to this service
query = (
sa.select(
resource_tracker_pricing_plans.c.pricing_plan_id,
resource_tracker_pricing_plans.c.display_name,
resource_tracker_pricing_plans.c.description,
resource_tracker_pricing_plans.c.classification,
resource_tracker_pricing_plans.c.is_active,
resource_tracker_pricing_plans.c.created,
resource_tracker_pricing_plans.c.pricing_plan_key,
resource_tracker_pricing_plan_to_service.c.service_default_plan,
)
& (
resource_tracker_pricing_plan_to_service.c.service_key
== latest_service_key
.select_from(
resource_tracker_pricing_plan_to_service.join(
resource_tracker_pricing_plans,
(
resource_tracker_pricing_plan_to_service.c.pricing_plan_id
== resource_tracker_pricing_plans.c.pricing_plan_id
),
)
)
.where(
(
_version(
resource_tracker_pricing_plan_to_service.c.service_version
)
== _version(latest_service_version)
)
& (
resource_tracker_pricing_plan_to_service.c.service_key
== latest_service_key
)
& (resource_tracker_pricing_plans.c.product_name == product_name)
& (resource_tracker_pricing_plans.c.is_active.is_(True))
)
.order_by(
resource_tracker_pricing_plan_to_service.c.pricing_plan_id.desc()
)
& (resource_tracker_pricing_plans.c.product_name == product_name)
& (resource_tracker_pricing_plans.c.is_active.is_(True))
).order_by(
resource_tracker_pricing_plan_to_service.c.pricing_plan_id.desc()
)
result = await conn.execute(query)

Expand All @@ -473,6 +496,7 @@ def _pricing_units_select_stmt():
resource_tracker_pricing_units.c.pricing_unit_id,
resource_tracker_pricing_units.c.pricing_plan_id,
resource_tracker_pricing_units.c.unit_name,
resource_tracker_pricing_units.c.unit_attributes,
resource_tracker_pricing_units.c.default,
resource_tracker_pricing_units.c.specific_info,
resource_tracker_pricing_units.c.created,
Expand Down Expand Up @@ -567,8 +591,8 @@ async def get_pricing_unit(

row = result.first()
if row is None:
raise ResourceUsageTrackerCustomRuntimeError(
msg=f"Pricing unit id {pricing_unit_id} not found"
raise MyHTTPException(
status_code=404, detail=f"Pricing unit id {pricing_unit_id} not found"
)
return PricingUnitsDB.from_orm(row)

Expand All @@ -589,7 +613,6 @@ async def get_pricing_unit_cost_by_id(
resource_tracker_pricing_unit_costs.c.cost_per_unit,
resource_tracker_pricing_unit_costs.c.valid_from,
resource_tracker_pricing_unit_costs.c.valid_to,
resource_tracker_pricing_unit_costs.c.specific_info,
resource_tracker_pricing_unit_costs.c.created,
resource_tracker_pricing_unit_costs.c.comment,
resource_tracker_pricing_unit_costs.c.modified,
Expand All @@ -601,7 +624,8 @@ async def get_pricing_unit_cost_by_id(

row = result.first()
if row is None:
raise ResourceUsageTrackerCustomRuntimeError(
msg=f"Pricing unit cosd id {pricing_unit_cost_id} not found in the resource_tracker_pricing_unit_costs table"
raise MyHTTPException(
status_code=404,
detail=f"Pricing unit cosd id {pricing_unit_cost_id} not found in the resource_tracker_pricing_unit_costs table",
)
return PricingUnitCostsDB.from_orm(row)
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
from models_library.products import ProductName
from models_library.resource_tracker import PricingPlanId, PricingUnitId
from models_library.services import ServiceKey, ServiceVersion
from simcore_service_resource_usage_tracker.core.errors import MyHTTPException

from ..api.dependencies import get_repository
from ..core.errors import ResourceUsageTrackerCustomRuntimeError
from ..modules.db.repositories.resource_tracker import ResourceTrackerRepository


Expand All @@ -33,9 +33,7 @@ async def get_service_default_pricing_plan(
break

if default_pricing_plan is None:
raise ResourceUsageTrackerCustomRuntimeError(
msg="No default pricing plan for the specified service"
)
raise MyHTTPException(404, "No default pricing plan for the specified service")

pricing_plan_unit_db = (
await resource_tracker_repo.list_pricing_units_by_pricing_plan(
Expand All @@ -54,6 +52,7 @@ async def get_service_default_pricing_plan(
PricingUnitGet(
pricing_unit_id=unit.pricing_unit_id,
unit_name=unit.unit_name,
unit_attributes=unit.unit_attributes,
current_cost_per_unit=unit.current_cost_per_unit,
current_cost_per_unit_id=unit.current_cost_per_unit_id,
default=unit.default,
Expand All @@ -79,6 +78,7 @@ async def get_pricing_unit(
return PricingUnitGet(
pricing_unit_id=pricing_unit.pricing_unit_id,
unit_name=pricing_unit.unit_name,
unit_attributes=pricing_unit.unit_attributes,
current_cost_per_unit=pricing_unit.current_cost_per_unit,
current_cost_per_unit_id=pricing_unit.current_cost_per_unit_id,
default=pricing_unit.default,
Expand Down
Loading