Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
55 changes: 37 additions & 18 deletions apis/vshn/v1/billing_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,40 @@ type BillingServiceSpec struct {
Odoo OdooSpec `json:"odoo,omitempty"`
}

// ItemSpec defines a single billable product/item
type ItemSpec struct {
// ProductID identifies the product in the billing system
ProductID string `json:"productID"`

// ItemDescription is a human-readable description of the billing item
ItemDescription string `json:"itemDescription,omitempty"`

// ItemGroupDescription describes the billing item group
ItemGroupDescription string `json:"itemGroupDescription,omitempty"`

// Unit defines the billing unit type for this product
Unit string `json:"unit,omitempty"`

// Value represents the billable metric for this product
// Can be: replica count, disk size (e.g., "50Gi"), percentage, etc.
Value string `json:"value"`
}

// OdooSpec defines Odoo-specific billing configuration
type OdooSpec struct {
// InstanceID uniquely identifies the service instance in Odoo
InstanceID string `json:"instanceID"`

// ProductID identifies the product in the billing system
ProductID string `json:"productID"`

// SalesOrderID identifies the sales order in Odoo
SalesOrderID string `json:"salesOrderID,omitempty"`

// Organization used to identify sales order
Organization string `json:"organization,omitempty"`

// UnitID defines the billing unit type in Odoo
UnitID string `json:"unitID"`

// Size represents the size of the service instance
Size string `json:"size,omitempty"`

// ItemGroupDescription describes the billing item group
ItemGroupDescription string `json:"itemGroupDescription"`

// ItemDescription is a human readable description of the billing item
ItemDescription string `json:"itemDescription"`
// Items defines list of billable products for this instance
// Each item represents a product with independent lifecycle and event tracking
// +kubebuilder:validation:MinItems=1
Items []ItemSpec `json:"items"`
}

// BillingServiceStatus defines the observed state of a BillingService
Expand All @@ -76,15 +85,25 @@ type BillingServiceStatus struct {

// BillingEventStatus represents the status of a billing event
type BillingEventStatus struct {
// Type is the type of billing event (created, deleted, scaled)
// +kubebuilder:validation:Enum="created";"deleted";"scaled"
// Type is the type of billing event (create, delete, scale)
// +kubebuilder:validation:Enum="create";"delete";"scale"
Type string `json:"type"`

// ProductID identifies the product in the billing system
ProductID string `json:"productId"`

// Size represents the size/plan at the time of the event
Size string `json:"size"`
// Value represents the billable metric at the time of the event
// Generic field supporting replica count, disk size, percentages, etc.
Value string `json:"value"`

// Unit defines the billing unit type for this product
Unit string `json:"unit,omitempty"`

// ItemDescription is a human-readable description of the billing item
ItemDescription string `json:"itemDescription,omitempty"`

// ItemGroupDescription describes the billing item group
ItemGroupDescription string `json:"itemGroupDescription,omitempty"`

// Timestamp when the event occurred
Timestamp metav1.Time `json:"timestamp"`
Expand Down
24 changes: 22 additions & 2 deletions apis/vshn/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion cmd/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ func (c *controller) executeController(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("initialize Odoo client: %w", err)
}

b := billing.New(mgr.GetClient(), mgr.GetScheme(), odooClient)
maxEvents := viper.GetInt("BILLING_MAX_EVENTS_PRODUCT")
if maxEvents <= 0 {
maxEvents = 100 // default
}

b := billing.New(mgr.GetClient(), mgr.GetScheme(), odooClient, maxEvents)
if err := b.SetupWithManager(mgr); err != nil {
return err
}
Expand Down
9 changes: 0 additions & 9 deletions config/controller/cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,9 @@ rules:
- xvshnpostgresqls/status
- xvshnredis
- xvshnredis/status
- xobjectbuckets
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- apiextensions.crossplane.io
resources:
- compositeresourcedefinitions
verbs:
- get
- list
- watch
87 changes: 57 additions & 30 deletions crds/vshn.appcat.vshn.io_billingservices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,34 +62,49 @@ spec:
description: InstanceID uniquely identifies the service instance
in Odoo
type: string
itemDescription:
description: ItemDescription is a human readable description of
the billing item
type: string
itemGroupDescription:
description: ItemGroupDescription describes the billing item group
type: string
items:
description: |-
Items defines list of billable products for this instance
Each item represents a product with independent lifecycle and event tracking
items:
description: ItemSpec defines a single billable product/item
properties:
itemDescription:
description: ItemDescription is a human-readable description
of the billing item
type: string
itemGroupDescription:
description: ItemGroupDescription describes the billing
item group
type: string
productID:
description: ProductID identifies the product in the billing
system
type: string
unit:
description: Unit defines the billing unit type for this
product
type: string
value:
description: |-
Value represents the billable metric for this product
Can be: replica count, disk size (e.g., "50Gi"), percentage, etc.
type: string
required:
- productID
- value
type: object
minItems: 1
type: array
organization:
description: Organization used to identify sales order
type: string
productID:
description: ProductID identifies the product in the billing system
type: string
salesOrderID:
description: SalesOrderID identifies the sales order in Odoo
type: string
size:
description: Size represents the size of the service instance
type: string
unitID:
description: UnitID defines the billing unit type in Odoo
type: string
required:
- instanceID
- itemDescription
- itemGroupDescription
- productID
- unitID
- items
type: object
type: object
status:
Expand Down Expand Up @@ -159,6 +174,14 @@ spec:
description: BillingEventStatus represents the status of a billing
event
properties:
itemDescription:
description: ItemDescription is a human-readable description
of the billing item
type: string
itemGroupDescription:
description: ItemGroupDescription describes the billing item
group
type: string
lastAttemptTime:
description: LastAttemptTime is when we last tried to send this
event
Expand All @@ -173,10 +196,6 @@ spec:
description: RetryCount tracks the number of retry attempts
for failed events
type: integer
size:
description: Size represents the size/plan at the time of the
event
type: string
state:
description: State represents the current state of the event
(sent, pending, failed, superseded)
Expand All @@ -191,19 +210,27 @@ spec:
format: date-time
type: string
type:
description: Type is the type of billing event (created, deleted,
scaled)
description: Type is the type of billing event (create, delete,
scale)
enum:
- created
- deleted
- scaled
- create
- delete
- scale
type: string
unit:
description: Unit defines the billing unit type for this product
type: string
value:
description: |-
Value represents the billable metric at the time of the event
Generic field supporting replica count, disk size, percentages, etc.
type: string
required:
- productId
- size
- state
- timestamp
- type
- value
type: object
type: array
type: object
Expand Down
51 changes: 26 additions & 25 deletions pkg/comp-functions/functions/common/billing_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ const (
type BillingServiceOptions struct {
// ResourceNameSuffix is appended to comp.GetName() to form the resource name (e.g., "-billing-service", "-addon-collabora")
ResourceNameSuffix string
// ProductID overrides the auto-generated productID based on service type and sla
ProductID string
// Size overrides the replica count for billing purposes
Size string
// Items defines the list of billable items/products for this instance
Items []vshnv1.ItemSpec
// AdditionalLabels are added to the BillingService CR labels
AdditionalLabels map[string]string
}
Expand Down Expand Up @@ -64,13 +62,7 @@ func CreateOrUpdateBillingServiceWithOptions(ctx context.Context, svc *runtime.S
namespace := comp.GetClaimNamespace()
service := comp.GetServiceName()

// Create productID from service and number of replicas (or use override)
productID := opts.ProductID
if productID == "" {
productID = getProductID(comp.GetInstances(), service)
}

// Get unitID from config
// Get unitID from config (for default item if no items specified)
unitID := svc.Config.Data["billingUnitID"]
if unitID == "" {
log.Error(fmt.Errorf("missing billing unitID"), "UnitID missing in composition")
Expand Down Expand Up @@ -98,10 +90,23 @@ func CreateOrUpdateBillingServiceWithOptions(ctx context.Context, svc *runtime.S
isAPPUiOCloud = true
}

// Determine size (use override or instance count)
size := opts.Size
if size == "" {
size = strconv.Itoa(comp.GetInstances())
// Prepare ItemGroupDescription and ItemDescription for all items
itemGroupDescription := claim
itemDescription := GetItemDescription(isAPPUiOCloud, clusterName, namespace)

// If no items specified, create default compute item
items := opts.Items
if len(items) == 0 {
productID := getProductID(comp, service)
items = []vshnv1.ItemSpec{
{
ProductID: productID,
Value: strconv.Itoa(comp.GetInstances()),
Unit: unitID,
ItemDescription: itemDescription,
ItemGroupDescription: itemGroupDescription,
},
}
}

// Build labels
Expand All @@ -124,13 +129,9 @@ func CreateOrUpdateBillingServiceWithOptions(ctx context.Context, svc *runtime.S
Spec: vshnv1.BillingServiceSpec{
KeepAfterDeletion: keepAfterDeletion,
Odoo: vshnv1.OdooSpec{
InstanceID: comp.GetName(),
ProductID: productID,
UnitID: unitID,
Size: size,
SalesOrderID: salesOrder,
ItemGroupDescription: claim,
ItemDescription: GetItemDescription(isAPPUiOCloud, clusterName, namespace),
InstanceID: comp.GetName(),
SalesOrderID: salesOrder,
Items: items,
},
},
}
Expand Down Expand Up @@ -180,7 +181,7 @@ func CreateOrUpdateBillingServiceWithOptions(ctx context.Context, svc *runtime.S
return runtime.NewWarningResult(fmt.Sprintf("cannot create BillingService for %s: %v", comp.GetName(), err))
}

return runtime.NewNormalResult(fmt.Sprintf("BillingService configured for instance %s", comp.GetName()))
return runtime.NewNormalResult(fmt.Sprintf("BillingService configured for instance %s with %d items", comp.GetName(), len(items)))
}

// GetItemDescription returns item description with cluster and namespace name
Expand All @@ -191,9 +192,9 @@ func GetItemDescription(isAPPUiOCloud bool, cluster, namespace string) string {
return fmt.Sprintf("APPUiO Managed - Cluster: %s / Namespace: %s", cluster, namespace)
}

func getProductID(instances int, service string) string {
func getProductID(comp InfoGetter, service string) string {
sla := vshnv1.BestEffort
if instances > 1 {
if comp.GetInstances() > 1 && comp.GetSLA() == string(vshnv1.Guaranteed) {
sla = vshnv1.Guaranteed
}

Expand Down
Loading
Loading