Add billing annotations and tests
This commit is contained in:
parent
3e6623278c
commit
84c0220af0
2 changed files with 258 additions and 7 deletions
|
|
@ -715,10 +715,18 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
return spec_data
|
||||
|
||||
@staticmethod
|
||||
def _build_billing_annotations(compute_plan_assignment, control_plane):
|
||||
def _build_billing_annotations(
|
||||
compute_plan_assignment,
|
||||
control_plane,
|
||||
instance_name=None,
|
||||
organization=None,
|
||||
service=None,
|
||||
):
|
||||
"""
|
||||
Build Kubernetes annotations for billing integration.
|
||||
"""
|
||||
from servala.core.models.organization import InvoiceGroupingChoice
|
||||
|
||||
annotations = {}
|
||||
|
||||
if compute_plan_assignment:
|
||||
|
|
@ -738,6 +746,30 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
control_plane.storage_plan_odoo_unit_id
|
||||
)
|
||||
|
||||
if organization and instance_name:
|
||||
invoice_grouping = organization.origin.invoice_grouping
|
||||
cloud_provider_name = control_plane.cloud_provider.name
|
||||
control_plane_name = control_plane.name
|
||||
|
||||
if invoice_grouping == InvoiceGroupingChoice.BY_SERVICE:
|
||||
if service:
|
||||
annotations["servala.com/erp_item_group_description"] = (
|
||||
f"Servala Service: {service.name}"
|
||||
)
|
||||
annotations["servala.com/erp_item_description"] = (
|
||||
f"{instance_name} on {cloud_provider_name} {control_plane_name}"
|
||||
)
|
||||
else:
|
||||
group_description = f"Organization: {organization.name}"
|
||||
item_description = f"{instance_name} on {control_plane_name}"
|
||||
|
||||
if organization.osb_guid:
|
||||
group_description += f" ({organization.osb_guid})"
|
||||
item_description += f" [Org: {organization.osb_guid}]"
|
||||
|
||||
annotations["servala.com/erp_item_group_description"] = group_description
|
||||
annotations["servala.com/erp_item_description"] = item_description
|
||||
|
||||
return annotations
|
||||
|
||||
@classmethod
|
||||
|
|
@ -846,7 +878,11 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
}
|
||||
|
||||
annotations = cls._build_billing_annotations(
|
||||
compute_plan_assignment, context.control_plane
|
||||
compute_plan_assignment=compute_plan_assignment,
|
||||
control_plane=context.control_plane,
|
||||
instance_name=name,
|
||||
organization=organization,
|
||||
service=context.service_offering.service,
|
||||
)
|
||||
if annotations:
|
||||
create_data["metadata"]["annotations"] = annotations
|
||||
|
|
@ -900,7 +936,11 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
patch_body = {"spec": spec_data}
|
||||
|
||||
annotations = self._build_billing_annotations(
|
||||
plan_to_use, self.context.control_plane
|
||||
compute_plan_assignment=plan_to_use,
|
||||
control_plane=self.context.control_plane,
|
||||
instance_name=self.name,
|
||||
organization=self.organization,
|
||||
service=self.context.service_offering.service,
|
||||
)
|
||||
if annotations:
|
||||
patch_body["metadata"] = {"annotations": annotations}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import pytest
|
|||
from servala.core.models import (
|
||||
ComputePlan,
|
||||
ComputePlanAssignment,
|
||||
InvoiceGroupingChoice,
|
||||
ServiceInstance,
|
||||
)
|
||||
|
||||
|
|
@ -125,7 +126,8 @@ def test_build_billing_annotations_complete():
|
|||
control_plane.storage_plan_odoo_unit_id = "storage-unit-id"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment, control_plane
|
||||
compute_plan_assignment=compute_plan_assignment,
|
||||
control_plane=control_plane,
|
||||
)
|
||||
|
||||
assert annotations["servala.com/erp_product_id_resource"] == "test-product-123"
|
||||
|
|
@ -140,7 +142,10 @@ def test_build_billing_annotations_no_compute_plan():
|
|||
control_plane.storage_plan_odoo_product_id = "storage-product-id"
|
||||
control_plane.storage_plan_odoo_unit_id = "storage-unit-id"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(None, control_plane)
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=None,
|
||||
control_plane=control_plane,
|
||||
)
|
||||
|
||||
assert "servala.com/erp_product_id_resource" not in annotations
|
||||
assert "servala.com/erp_unit_id_resource" not in annotations
|
||||
|
|
@ -158,7 +163,8 @@ def test_build_billing_annotations_no_storage_plan():
|
|||
control_plane.storage_plan_odoo_unit_id = None
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment, control_plane
|
||||
compute_plan_assignment=compute_plan_assignment,
|
||||
control_plane=control_plane,
|
||||
)
|
||||
|
||||
assert annotations["servala.com/erp_product_id_resource"] == "product-id"
|
||||
|
|
@ -172,7 +178,10 @@ def test_build_billing_annotations_empty():
|
|||
control_plane.storage_plan_odoo_product_id = None
|
||||
control_plane.storage_plan_odoo_unit_id = None
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(None, control_plane)
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=None,
|
||||
control_plane=control_plane,
|
||||
)
|
||||
|
||||
assert annotations == {}
|
||||
|
||||
|
|
@ -197,3 +206,205 @@ def test_all_billing_units():
|
|||
assert str(choices["day"]) == "Day"
|
||||
assert "Month" in str(choices["month"])
|
||||
assert str(choices["year"]) == "Year"
|
||||
|
||||
|
||||
def test_build_billing_annotations_by_service_grouping():
|
||||
control_plane = Mock()
|
||||
control_plane.storage_plan_odoo_product_id = None
|
||||
control_plane.storage_plan_odoo_unit_id = None
|
||||
control_plane.cloud_provider = Mock()
|
||||
control_plane.cloud_provider.name = "Exoscale"
|
||||
control_plane.name = "Geneva (CH-GVA-2)"
|
||||
|
||||
organization = Mock()
|
||||
organization.name = "ACME Corp"
|
||||
organization.osb_guid = "01998651-dc86-7d43-9e49-cdb790fcc4f0"
|
||||
organization.origin = Mock()
|
||||
organization.origin.invoice_grouping = InvoiceGroupingChoice.BY_SERVICE
|
||||
|
||||
service = Mock()
|
||||
service.name = "Redis"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=None,
|
||||
control_plane=control_plane,
|
||||
instance_name="MyProdRedis",
|
||||
organization=organization,
|
||||
service=service,
|
||||
)
|
||||
|
||||
assert (
|
||||
annotations["servala.com/erp_item_group_description"]
|
||||
== "Servala Service: Redis"
|
||||
)
|
||||
assert (
|
||||
annotations["servala.com/erp_item_description"]
|
||||
== "MyProdRedis on Exoscale Geneva (CH-GVA-2)"
|
||||
)
|
||||
|
||||
|
||||
def test_build_billing_annotations_by_organization_grouping_with_osb_guid():
|
||||
control_plane = Mock()
|
||||
control_plane.storage_plan_odoo_product_id = None
|
||||
control_plane.storage_plan_odoo_unit_id = None
|
||||
control_plane.cloud_provider = Mock()
|
||||
control_plane.cloud_provider.name = "Exoscale"
|
||||
control_plane.name = "Geneva (CH-GVA-2)"
|
||||
|
||||
organization = Mock()
|
||||
organization.name = "ACME"
|
||||
organization.osb_guid = "01998651-dc86-7d43-9e49-cdb790fcc4f0"
|
||||
organization.origin = Mock()
|
||||
organization.origin.invoice_grouping = InvoiceGroupingChoice.BY_ORGANIZATION
|
||||
|
||||
service = Mock()
|
||||
service.name = "Redis"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=None,
|
||||
control_plane=control_plane,
|
||||
instance_name="MyProdRedis",
|
||||
organization=organization,
|
||||
service=service,
|
||||
)
|
||||
|
||||
assert (
|
||||
annotations["servala.com/erp_item_group_description"]
|
||||
== "Organization: ACME (01998651-dc86-7d43-9e49-cdb790fcc4f0)"
|
||||
)
|
||||
assert (
|
||||
annotations["servala.com/erp_item_description"]
|
||||
== "MyProdRedis on Geneva (CH-GVA-2) [Org: 01998651-dc86-7d43-9e49-cdb790fcc4f0]"
|
||||
)
|
||||
|
||||
|
||||
def test_build_billing_annotations_by_organization_grouping_without_osb_guid():
|
||||
control_plane = Mock()
|
||||
control_plane.storage_plan_odoo_product_id = None
|
||||
control_plane.storage_plan_odoo_unit_id = None
|
||||
control_plane.cloud_provider = Mock()
|
||||
control_plane.cloud_provider.name = "Exoscale"
|
||||
control_plane.name = "Geneva (CH-GVA-2)"
|
||||
|
||||
organization = Mock()
|
||||
organization.name = "ACME Corp"
|
||||
organization.osb_guid = None # No OSB GUID
|
||||
organization.origin = Mock()
|
||||
organization.origin.invoice_grouping = InvoiceGroupingChoice.BY_ORGANIZATION
|
||||
|
||||
service = Mock()
|
||||
service.name = "Redis"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=None,
|
||||
control_plane=control_plane,
|
||||
instance_name="MyProdRedis",
|
||||
organization=organization,
|
||||
service=service,
|
||||
)
|
||||
|
||||
assert (
|
||||
annotations["servala.com/erp_item_group_description"]
|
||||
== "Organization: ACME Corp"
|
||||
)
|
||||
assert (
|
||||
annotations["servala.com/erp_item_description"]
|
||||
== "MyProdRedis on Geneva (CH-GVA-2)"
|
||||
)
|
||||
|
||||
|
||||
def test_build_billing_annotations_by_organization_grouping_with_empty_osb_guid():
|
||||
control_plane = Mock()
|
||||
control_plane.storage_plan_odoo_product_id = None
|
||||
control_plane.storage_plan_odoo_unit_id = None
|
||||
control_plane.cloud_provider = Mock()
|
||||
control_plane.cloud_provider.name = "Exoscale"
|
||||
control_plane.name = "Geneva (CH-GVA-2)"
|
||||
|
||||
organization = Mock()
|
||||
organization.name = "ACME Corp"
|
||||
organization.osb_guid = "" # Empty string OSB GUID
|
||||
organization.origin = Mock()
|
||||
organization.origin.invoice_grouping = InvoiceGroupingChoice.BY_ORGANIZATION
|
||||
|
||||
service = Mock()
|
||||
service.name = "Redis"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=None,
|
||||
control_plane=control_plane,
|
||||
instance_name="MyProdRedis",
|
||||
organization=organization,
|
||||
service=service,
|
||||
)
|
||||
|
||||
assert (
|
||||
annotations["servala.com/erp_item_group_description"]
|
||||
== "Organization: ACME Corp"
|
||||
)
|
||||
assert (
|
||||
annotations["servala.com/erp_item_description"]
|
||||
== "MyProdRedis on Geneva (CH-GVA-2)"
|
||||
)
|
||||
|
||||
|
||||
def test_build_billing_annotations_no_item_description_without_organization():
|
||||
control_plane = Mock()
|
||||
control_plane.storage_plan_odoo_product_id = "storage-product"
|
||||
control_plane.storage_plan_odoo_unit_id = "storage-unit"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=None,
|
||||
control_plane=control_plane,
|
||||
instance_name="MyProdRedis",
|
||||
organization=None, # No organization
|
||||
service=None,
|
||||
)
|
||||
|
||||
assert "servala.com/erp_item_group_description" not in annotations
|
||||
assert "servala.com/erp_item_description" not in annotations
|
||||
# Storage annotations should still be there
|
||||
assert annotations["servala.com/erp_product_id_storage"] == "storage-product"
|
||||
|
||||
|
||||
def test_build_billing_annotations_combined():
|
||||
compute_plan_assignment = Mock()
|
||||
compute_plan_assignment.odoo_product_id = "compute-product"
|
||||
compute_plan_assignment.odoo_unit_id = "compute-unit"
|
||||
|
||||
control_plane = Mock()
|
||||
control_plane.storage_plan_odoo_product_id = "storage-product"
|
||||
control_plane.storage_plan_odoo_unit_id = "storage-unit"
|
||||
control_plane.cloud_provider = Mock()
|
||||
control_plane.cloud_provider.name = "Exoscale"
|
||||
control_plane.name = "Geneva (CH-GVA-2)"
|
||||
|
||||
organization = Mock()
|
||||
organization.name = "ACME"
|
||||
organization.osb_guid = "test-guid"
|
||||
organization.origin = Mock()
|
||||
organization.origin.invoice_grouping = InvoiceGroupingChoice.BY_SERVICE
|
||||
|
||||
service = Mock()
|
||||
service.name = "PostgreSQL"
|
||||
|
||||
annotations = ServiceInstance._build_billing_annotations(
|
||||
compute_plan_assignment=compute_plan_assignment,
|
||||
control_plane=control_plane,
|
||||
instance_name="ProdDB",
|
||||
organization=organization,
|
||||
service=service,
|
||||
)
|
||||
|
||||
assert annotations["servala.com/erp_product_id_resource"] == "compute-product"
|
||||
assert annotations["servala.com/erp_unit_id_resource"] == "compute-unit"
|
||||
assert annotations["servala.com/erp_product_id_storage"] == "storage-product"
|
||||
assert annotations["servala.com/erp_unit_id_storage"] == "storage-unit"
|
||||
assert (
|
||||
annotations["servala.com/erp_item_group_description"]
|
||||
== "Servala Service: PostgreSQL"
|
||||
)
|
||||
assert (
|
||||
annotations["servala.com/erp_item_description"]
|
||||
== "ProdDB on Exoscale Geneva (CH-GVA-2)"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue