Manage service plans and the associated Odoo product information #264

Open
opened 2025-10-30 10:46:21 +00:00 by tobru · 7 comments
Owner

Stories

As a user, I want to select a plan when ordering or changing a service instance.

Implementation Notes

Context

Servala service pricing depends on compute and storage plans. The plan influences the service configuration and pricing.
Example: https://servala.com/offering/cloudscale/forgejo/#plans

We need to track these plans in the Servala portal databases, offer them to the user in the service configuration form, and configure the service instance according to the chosen plan.

Plans are specific to the service and the control plane. For this initial iteration, we assign service plans in the ControlPlaneCRD model and offer the associated plans to the user. In a later iteration, we might want to offer plans also in other combinations, for example, a plan that is only available for a specific "Organization+ControlPlaneCRD" combination.

Compute Plans

  • Name
  • Amount of compute allocated to the service instance:
    • Memory requests
    • Memory limits
    • vCPU requests
    • vCPU limits

Storage plans

As they are currently hardcoded per control-plan, add two new field to the control-plane:

  • Storage plan Odoo product ID
  • Storage plan Odoo unit ID
  • Storage plan price per GiB

Products for Services

A product (usually a product variant) in Odoo represents a service with a specific compute plan, a control-plane and a service level (SLA). Therefore, it makes the most sense to store this correlation in the ControlPlaneCRD model.

In this model, we should be able to choose from the available compute plans (one or many), and for each chosen compute plan configure:

  • Odoo product ID
  • Odoo unit ID
  • Minimum service size
  • SLA
  • Price

This also means a compute plane can be added multiple times, but with differing SLA and pricing.
Make the SLA a text choice field with the choices of "Best Effort" and "Guaranteed Availability".
The field "Minimum service size" will be later used to set the minimum of the field spec.parameters.instances, because the service level "Guaranteed Availability" can set some constraints on this configuration.

Service instance creation

When creating or editing a service instance, allow the user to choose a compute plan and an available SLA per the chosen compute plan. Based on this choice:

  • Configure the service instance resources by setting the values in spec.parameters.size to the configuration in the compute plan:
    • spec.parameters.size.memory: Memory limits
    • spec.parameters.size.requests.memory: RAM requests
    • spec.parameters.size.cpu: vCPU limits
    • spec.parameters.size.requests.cpu: vCPU requests
  • Set the instance annotations:
    • servala.com/erp_product_id_resource: $ControlPlaneCRD.compute_plan.reporting_product_id
    • servala.com/erp_unit_id_resource: $ControlPlaneCRD.compute_plan.unit_id
  • Set the service level:
    • spec.parameters.service.serviceLevel: besteffort or guaranteed

In the frontend, show the details to the user:

  • Name of the compute plane
  • Name of the chosen SLA
  • Amount of RAM and vCPU
  • Price of the plan

Storage plans

Storage plans are hardcoded at the control plane and can't be chosen by the user at this time. Set the instance annotations from the data in the control plane configuration.

  • servala.com/erp_product_id_storage = $StoragePlan.reporting_product_id
  • servala.com/erp_unit_id_storage = $StoragePlan.unit_id

In the frontend, show the plan details to the user: storage price multiplied with the configured size.

## Stories _As a user, I want to select a plan when ordering or changing a service instance._ ## Implementation Notes ### Context Servala service pricing depends on compute and storage plans. The plan influences the service configuration and pricing. Example: https://servala.com/offering/cloudscale/forgejo/#plans We need to track these plans in the Servala portal databases, offer them to the user in the service configuration form, and configure the service instance according to the chosen plan. Plans are specific to the service and the control plane. For this initial iteration, we assign service plans in the `ControlPlaneCRD` model and offer the associated plans to the user. In a later iteration, we might want to offer plans also in other combinations, for example, a plan that is only available for a specific "Organization+ControlPlaneCRD" combination. ### Compute Plans * Name * Amount of compute allocated to the service instance: * Memory requests * Memory limits * vCPU requests * vCPU limits ### Storage plans As they are currently hardcoded per control-plan, add two new field to the control-plane: * Storage plan Odoo product ID * Storage plan Odoo unit ID * Storage plan price per GiB ## Products for Services A product (usually a product variant) in Odoo represents a service with a specific compute plan, a control-plane and a service level (SLA). Therefore, it makes the most sense to store this correlation in the `ControlPlaneCRD` model. In this model, we should be able to choose from the available compute plans (one or many), and for each chosen compute plan configure: * Odoo product ID * Odoo unit ID * Minimum service size * SLA * Price This also means a compute plane can be added multiple times, but with differing SLA and pricing. Make the SLA a text choice field with the choices of "Best Effort" and "Guaranteed Availability". The field "Minimum service size" will be later used to set the minimum of the field `spec.parameters.instances`, because the service level "Guaranteed Availability" can set some constraints on this configuration. ### Service instance creation When creating or editing a service instance, allow the user to choose a compute plan and an available SLA per the chosen compute plan. Based on this choice: * Configure the service instance resources by setting the values in `spec.parameters.size` to the configuration in the compute plan: * `spec.parameters.size.memory`: Memory limits * `spec.parameters.size.requests.memory`: RAM requests * `spec.parameters.size.cpu`: vCPU limits * `spec.parameters.size.requests.cpu`: vCPU requests * Set the instance annotations: * `servala.com/erp_product_id_resource`: `$ControlPlaneCRD.compute_plan.reporting_product_id` * `servala.com/erp_unit_id_resource`: `$ControlPlaneCRD.compute_plan.unit_id` * Set the service level: * `spec.parameters.service.serviceLevel`: `besteffort` or `guaranteed` In the frontend, show the details to the user: * Name of the compute plane * Name of the chosen SLA * Amount of RAM and vCPU * Price of the plan ### Storage plans Storage plans are hardcoded at the control plane and can't be chosen by the user at this time. Set the instance annotations from the data in the control plane configuration. * `servala.com/erp_product_id_storage = $StoragePlan.reporting_product_id` * `servala.com/erp_unit_id_storage = $StoragePlan.unit_id` In the frontend, show the plan details to the user: storage price multiplied with the configured size.
tobru added the
enhancement
label 2025-10-30 10:46:21 +00:00
tobru added this to the Development Planning project 2025-10-30 10:46:21 +00:00
tobru changed title from Manage service plans and the associated ERP products to Manage service plans and the associated Odoo product information 2025-10-30 15:39:47 +00:00
tobru added
Billing
and removed
enhancement
labels 2025-10-30 15:45:59 +00:00
Member

Some questions to aid with the data modeling:

  • Will one Odoo Product appear in multiple Plans in Servala?
  • Will Plan objects be re-used (that is, will they be attached to multiple CRDs)?
  • Should k8s be the single source of truth for the Instance : Plan matching by way of the annotations we provide, or should we additionally also connect Instance and Plan on Servala?
  • Just to confirm: data like "Memory limits" will be maintained in Servala and is not going to be fetched from Odoo?
Some questions to aid with the data modeling: - Will one Odoo Product appear in multiple Plans in Servala? - Will Plan objects be re-used (that is, will they be attached to multiple CRDs)? - Should k8s be the single source of truth for the Instance : Plan matching by way of the annotations we provide, or should we additionally also connect Instance and Plan on Servala? - Just to confirm: data like "Memory limits" will be maintained in Servala and is not going to be fetched from Odoo?
Author
Owner

Thanks to these questions, I found a fundamental flaw in the initial description I made: plans have nothing to do with products in Odoo. The connection to the product happens in a different place, see updated description.

Also, I decided to not yet connect to Odoo to query for data; let's do that in a follow-up issue when we finish this one (to not do too much at the same time).

Should k8s be the single source of truth for the Instance : Plan matching by way of the annotations we provide, or should we additionally also connect Instance and Plan on Servala?

Let's also track this in the Servala portal database, not only in the control-plane

Just to confirm: data like "Memory limits" will be maintained in Servala and is not going to be fetched from Odoo?

Yes

Thanks to these questions, I found a fundamental flaw in the initial description I made: plans have nothing to do with products in Odoo. The connection to the product happens in a different place, see updated description. Also, I decided to not yet connect to Odoo to query for data; let's do that in a follow-up issue when we finish this one (to not do too much at the same time). > Should k8s be the single source of truth for the Instance : Plan matching by way of the annotations we provide, or should we additionally also connect Instance and Plan on Servala? Let's also track this in the Servala portal database, not only in the control-plane > Just to confirm: data like "Memory limits" will be maintained in Servala and is not going to be fetched from Odoo? Yes
Member

I have left my changes between the first version of this issue and the seconf version in place in my PR, for reference to see what changed according to my understanding. I have some more questions to go along with the data model:

  • The issue refers to the "reporting product ID" – is this always the same as the product ID or is the "reporting product" something different that we're retrieving from Odoo as a second step? I've assumed it's the same, just making sure.
  • Again, just making sure: at the end of the issue, you said that "Storage plans are hardcoded at the control plane" – that's referring to the ControlPlaneCRD as specified before, right?
  • Do we need to make provisions for plans changing, e.g within a billing period in the future? Or will we just ping Odoo with the change and it will take care of the accounting?
I have left my changes between the first version of this issue and the seconf version in place in my PR, for reference to see what changed according to my understanding. I have some more questions to go along with the data model: - The issue refers to the "reporting product ID" – is this always the same as the product ID or is the "reporting product" something different that we're retrieving from Odoo as a second step? I've assumed it's the same, just making sure. - Again, just making sure: at the end of the issue, you said that "Storage plans are hardcoded at the control plane" – that's referring to the ControlPlaneCRD as specified before, right? - Do we need to make provisions for plans changing, e.g within a billing period in the future? Or will we just ping Odoo with the change and it will take care of the accounting?
Author
Owner

The issue refers to the "reporting product ID" – is this always the same as the product ID or is the "reporting product" something different that we're retrieving from Odoo as a second step? I've assumed it's the same, just making sure.

It's the same. In the first version I wanted to overcomplicate things and store the "real" Odoo ID, but now we're using the reporting ID which we also set on the annotations. There's no need to look up things right now in Odoo. Just use the string added to the database. Oh, I see that the Odoo IDs are added as integer, they are actually strings. I forgot to mention this. Such a string can look like this: vshn_event_billing.uom_instance_hour or openshift-exoscale-workervcpu-standard.

Again, just making sure: at the end of the issue, you said that "Storage plans are hardcoded at the control plane" – that's referring to the ControlPlaneCRD as specified before, right?

As it is now, per record in the controlplane model. It's correct as it is right now.

Do we need to make provisions for plans changing, e.g within a billing period in the future? Or will we just ping Odoo with the change and it will take care of the accounting?

This should be handled by the backend. We can just switch the plan in an instance via the annotations; the Kubernetes controller responsible for that will then handle this case and send an appropriate event to Odoo. Odoo then knows what to do.

> The issue refers to the "reporting product ID" – is this always the same as the product ID or is the "reporting product" something different that we're retrieving from Odoo as a second step? I've assumed it's the same, just making sure. It's the same. In the first version I wanted to overcomplicate things and store the "real" Odoo ID, but now we're using the reporting ID which we also set on the annotations. There's no need to look up things right now in Odoo. Just use the string added to the database. Oh, I see that the Odoo IDs are added as integer, they are actually strings. I forgot to mention this. Such a string can look like this: `vshn_event_billing.uom_instance_hour` or `openshift-exoscale-workervcpu-standard`. > Again, just making sure: at the end of the issue, you said that "Storage plans are hardcoded at the control plane" – that's referring to the ControlPlaneCRD as specified before, right? As it is now, per record in the `controlplane` model. It's correct as it is right now. > Do we need to make provisions for plans changing, e.g within a billing period in the future? Or will we just ping Odoo with the change and it will take care of the accounting? This should be handled by the backend. We can just switch the plan in an instance via the annotations; the [Kubernetes controller](https://kb.vshn.ch/app-catalog/adr/0033-event-based-billing-oddo.html) responsible for that will then handle this case and send an appropriate event to Odoo. Odoo then knows what to do.
Author
Owner

We should also add a field "unit" to the "computeplanassignment" model, to communicate to the user what the price is for. For example, a service may cost CHF 42.00 per month or CHF 0.42 per hour. Let's make this a selection field, but it doesn't warrant a relation. Maybe a hardcoded list would be enough, which we can enhance should the need arise. For now, these units should be available: "hour", "day". "month (30 days / 720 hours)", "year".

We should also add a field "unit" to the "computeplanassignment" model, to communicate to the user what the price is for. For example, a service may cost CHF 42.00 per month or CHF 0.42 per hour. Let's make this a selection field, but it doesn't warrant a relation. Maybe a hardcoded list would be enough, which we can enhance should the need arise. For now, these units should be available: "hour", "day". "month (30 days / 720 hours)", "year".
Member

I have a couple of questions UI-wise:

  • We will show available plans on the instance creation page – how do we handle things when there are no plans available at all? Do we block the instance creation? (Should we block it before the user even gets there?)
  • We still show the service instance creation form, right? How do we deal with the fields that get automatically set from the plans (cpu, memory): do we hide them? Do we show them (and update them based on plan size) but make them read-only?

If we aim to cover the change/updating of chosen plans immediately, the same questions apply for the instance update form.

I have a couple of questions UI-wise: - We will show available plans on the instance creation page – how do we handle things when there are no plans available at all? Do we block the instance creation? (Should we block it before the user even gets there?) - We still show the service instance creation form, right? How do we deal with the fields that get automatically set from the plans (cpu, memory): do we hide them? Do we show them (and update them based on plan size) but make them read-only? If we aim to cover the change/updating of chosen plans immediately, the same questions apply for the instance update form.
Author
Owner

We will show available plans on the instance creation page – how do we handle things when there are no plans available at all? Do we block the instance creation? (Should we block it before the user even gets there?)

We don't allow the creation of such a service instance because plans are crucial for billing. I would even go so far as to not allow the user to get to the point of being able to create an instance.

We still show the service instance creation form, right? How do we deal with the fields that get automatically set from the plans (cpu, memory): do we hide them? Do we show them (and update them based on plan size) but make them read-only?

We show them, but make them read-only.

> We will show available plans on the instance creation page – how do we handle things when there are no plans available at all? Do we block the instance creation? (Should we block it before the user even gets there?) We don't allow the creation of such a service instance because plans are crucial for billing. I would even go so far as to not allow the user to get to the point of being able to create an instance. > We still show the service instance creation form, right? How do we deal with the fields that get automatically set from the plans (cpu, memory): do we hide them? Do we show them (and update them based on plan size) but make them read-only? We show them, but make them read-only.
Sign in to join this conversation.
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: servala/servala-portal#264
No description provided.