Skip to content

Commit

Permalink
Add billing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriel Saratura committed Dec 3, 2024
1 parent 1ac1d24 commit 8c4e9ec
Show file tree
Hide file tree
Showing 4 changed files with 494 additions and 31 deletions.
2 changes: 1 addition & 1 deletion apis/vshn/v1/vshn_nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (v *VSHNNextcloud) SetInstanceNamespaceStatus() {
v.Status.InstanceNamespace = v.GetInstanceNamespace()
}

// CollaboraSpec defines the desired state of a Collabora.
// CollaboraSpec defines the desired state of a Collabora instance.
type CollaboraSpec struct {
// Enabled enables the Collabora integration. It will autoconfigure the Collabora server URL in Your Nextcloud instance.
//+kubebuilder:default=false
Expand Down
45 changes: 15 additions & 30 deletions pkg/comp-functions/functions/common/billing.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package common

import (
"bytes"
"context"
_ "embed"
"fmt"
"text/template"

xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1"
v1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/vshn/appcat/v4/pkg/comp-functions/runtime"
Expand All @@ -20,8 +17,6 @@ type ServiceAddOns struct {
Instances int
}

var rawExpr = "vector({{.}})"

// CreateBillingRecord creates a new prometheus rule per each instance namespace
// The rule is skipped for any secondary service such as postgresql instance for nextcloud
// The skipping is based on whether label appuio.io/billing-name is set or not on instance namespace
Expand All @@ -34,10 +29,7 @@ func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp
return nil
}

expr, err := getExprFromTemplate(comp.GetInstances())
if err != nil {
runtime.NewWarningResult(fmt.Sprintf("cannot add billing to service %s", comp.GetName()))
}
expr := getVectorExpression(comp.GetInstances())

org, err := getOrg(comp.GetName(), svc)
if err != nil {
Expand Down Expand Up @@ -74,10 +66,7 @@ func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp

for _, addOn := range addOns {
log.Info("Adding billing addOn for service", "service", comp.GetName(), "addOn", addOn.Name)
exprAddOn, err := getExprFromTemplate(addOn.Instances)
if err != nil {
return runtime.NewWarningResult(fmt.Sprintf("cannot add addOn %s billing to service %s", addOn.Name, comp.GetName()))
}
exprAddOn := getVectorExpression(addOn.Instances)
rg.Rules = append(rg.Rules, v1.Rule{
Record: "appcat:metering",
Expr: intstr.FromString(exprAddOn),
Expand Down Expand Up @@ -107,11 +96,15 @@ func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp
}

func getLabels(svc *runtime.ServiceRuntime, comp InfoGetter, org, addOnName string) map[string]string {
b, err := getBillingNameWithAddOn(comp.GetBillingName(), addOnName)
if err != nil {
panic(fmt.Errorf("set billing name for service %s: %v", comp.GetServiceName(), err))
}
labels := map[string]string{
"label_appcat_vshn_io_claim_name": comp.GetClaimName(),
"label_appcat_vshn_io_claim_namespace": comp.GetClaimNamespace(),
"label_appcat_vshn_io_sla": comp.GetSLA(),
"label_appuio_io_billing_name": getFinalBillingName(comp.GetBillingName(), addOnName),
"label_appuio_io_billing_name": b,
"label_appuio_io_organization": org,
}

Expand All @@ -123,24 +116,16 @@ func getLabels(svc *runtime.ServiceRuntime, comp InfoGetter, org, addOnName stri
return labels
}

func getFinalBillingName(billingName, addOn string) string {
func getBillingNameWithAddOn(billingName, addOn string) (string, error) {
if billingName == "" {
return "", fmt.Errorf("billing name is empty")
}
if addOn == "" {
return billingName
return billingName, nil
}
return fmt.Sprintf("%s-%s", billingName, addOn)
return fmt.Sprintf("%s-%s", billingName, addOn), nil
}

func getExprFromTemplate(i int) (string, error) {
var buf bytes.Buffer
tmpl, err := template.New("billing").Parse(rawExpr)
if err != nil {
return "", err
}

err = tmpl.Execute(&buf, i)
if err != nil {
return "", err
}

return buf.String(), err
func getVectorExpression(i int) string {
return fmt.Sprintf("vector(%d)", i)
}
291 changes: 291 additions & 0 deletions pkg/comp-functions/functions/common/billing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package common

import (
"context"
_ "embed"
xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1"
v2 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/stretchr/testify/assert"
v1 "github.com/vshn/appcat/v4/apis/vshn/v1"
"github.com/vshn/appcat/v4/pkg/comp-functions/functions/commontest"
"github.com/vshn/appcat/v4/pkg/comp-functions/runtime"
api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"testing"
)

func TestBilling(t *testing.T) {

tests := []struct {
name string
comp *v1.VSHNNextcloud
svc *runtime.ServiceRuntime
org string
addOns []ServiceAddOns
expectedRuleGroup v2.RuleGroup
}{
{
name: "TestCreateBillingRecord_WhenServiceWithAddOn_ThenReturnMultipleRules",
comp: &v1.VSHNNextcloud{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"crossplane.io/claim-namespace": "test-namespace",
"crossplane.io/claim-name": "test-instance",
},
Name: "nextcloud-gc9x4",
Namespace: "unit-test",
},
Spec: v1.VSHNNextcloudSpec{
Parameters: v1.VSHNNextcloudParameters{
Instances: 1,
Service: v1.VSHNNextcloudServiceSpec{ServiceLevel: v1.BestEffort},
},
},
},
svc: commontest.LoadRuntimeFromFile(t, "common/billing.yaml"),
org: "APPUiO",
addOns: []ServiceAddOns{
{
Name: "office",
Instances: 1,
},
},
expectedRuleGroup: v2.RuleGroup{
Name: "appcat-metering-rules",
Rules: []v2.Rule{
{
Record: "appcat:metering",
Expr: intstr.IntOrString{
Type: 1,
StrVal: "vector(1)",
},
Labels: map[string]string{
"label_appcat_vshn_io_claim_name": "test-instance",
"label_appcat_vshn_io_claim_namespace": "test-namespace",
"label_appcat_vshn_io_sla": "besteffort",
"label_appuio_io_billing_name": "appcat-nextcloud",
"label_appuio_io_organization": "vshn",
},
},
{
Record: "appcat:metering",
Expr: intstr.IntOrString{
Type: 1,
StrVal: "vector(1)",
},
Labels: map[string]string{
"label_appcat_vshn_io_claim_name": "test-instance",
"label_appcat_vshn_io_claim_namespace": "test-namespace",
"label_appcat_vshn_io_sla": "besteffort",
"label_appuio_io_billing_name": "appcat-nextcloud-office",
"label_appuio_io_organization": "vshn",
},
},
},
},
},
{
name: "TestCreateBillingRecord_WhenServiceWithoutAddOn_ThenReturnSingleRule",
comp: &v1.VSHNNextcloud{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"crossplane.io/claim-namespace": "test-namespace",
"crossplane.io/claim-name": "test-instance",
},
Name: "nextcloud-gc9x4",
Namespace: "unit-test",
},
Spec: v1.VSHNNextcloudSpec{
Parameters: v1.VSHNNextcloudParameters{
Instances: 1,
Service: v1.VSHNNextcloudServiceSpec{ServiceLevel: v1.BestEffort},
},
},
},
svc: commontest.LoadRuntimeFromFile(t, "common/billing.yaml"),
org: "APPUiO",
addOns: []ServiceAddOns{},
expectedRuleGroup: v2.RuleGroup{
Name: "appcat-metering-rules",
Rules: []v2.Rule{
{
Record: "appcat:metering",
Expr: intstr.IntOrString{
Type: 1,
StrVal: "vector(1)",
},
Labels: map[string]string{
"label_appcat_vshn_io_claim_name": "test-instance",
"label_appcat_vshn_io_claim_namespace": "test-namespace",
"label_appcat_vshn_io_sla": "besteffort",
"label_appuio_io_billing_name": "appcat-nextcloud",
"label_appuio_io_organization": "vshn",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := CreateBillingRecord(context.Background(), tt.svc, tt.comp, tt.addOns...)
assert.Equal(t, xfnproto.Severity_SEVERITY_NORMAL, r.Severity)
actual := &v2.PrometheusRule{}
err := tt.svc.GetDesiredKubeObject(actual, "nextcloud-gc9x4-billing")
assert.NoError(t, err)
assert.Equal(t, tt.expectedRuleGroup, actual.Spec.Groups[0])

})
}
}

func TestGetLabels(t *testing.T) {
tests := []struct {
name string
comp *v1.VSHNNextcloud
svc *runtime.ServiceRuntime
org string
addOnName string
expectedLabels map[string]string
}{
{
name: "TestGetLabels_WhenManagedAndAddOn_ThenReturnLabels",
comp: &v1.VSHNNextcloud{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"crossplane.io/claim-namespace": "test-namespace",
"crossplane.io/claim-name": "test-instance",
},
},
Spec: v1.VSHNNextcloudSpec{
Parameters: v1.VSHNNextcloudParameters{
Service: v1.VSHNNextcloudServiceSpec{ServiceLevel: v1.BestEffort},
},
},
},
svc: &runtime.ServiceRuntime{
Config: api.ConfigMap{
Data: map[string]string{
"salesOrder": "12132ST",
},
},
},
org: "APPUiO",
addOnName: "office",
expectedLabels: map[string]string{
"label_appcat_vshn_io_claim_name": "test-instance",
"label_appcat_vshn_io_claim_namespace": "test-namespace",
"label_appcat_vshn_io_sla": "besteffort",
"label_appuio_io_billing_name": "appcat-nextcloud-office",
"label_appuio_io_organization": "APPUiO",
"sales_order": "12132ST",
},
},
{
name: "TestGetLabels_WhenNonManagedAndAddOn_ThenReturnLabels",
comp: &v1.VSHNNextcloud{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"crossplane.io/claim-namespace": "test-namespace",
"crossplane.io/claim-name": "test-instance",
},
},
Spec: v1.VSHNNextcloudSpec{
Parameters: v1.VSHNNextcloudParameters{
Service: v1.VSHNNextcloudServiceSpec{ServiceLevel: v1.BestEffort},
},
},
},
svc: &runtime.ServiceRuntime{},
org: "APPUiO",
addOnName: "office",
expectedLabels: map[string]string{
"label_appcat_vshn_io_claim_name": "test-instance",
"label_appcat_vshn_io_claim_namespace": "test-namespace",
"label_appcat_vshn_io_sla": "besteffort",
"label_appuio_io_billing_name": "appcat-nextcloud-office",
"label_appuio_io_organization": "APPUiO",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
labels := getLabels(tt.svc, tt.comp, tt.org, tt.addOnName)
assert.Equal(t, tt.expectedLabels, labels)
})
}
}

func TestGetBillingNameWithAddOn(t *testing.T) {
tests := []struct {
name string
billingName string
addOn string
result string
err bool
}{
{
name: "TestGetBillingNameWithAddOn_WhenAddOn_ThenBillingNameWithAddOn",
billingName: "nextcloud",
addOn: "office",
result: "nextcloud-office",
err: false,
},
{
name: "TestGetBillingNameWithAddOn_WhenNoAddOn_ThenJustBillingName",
billingName: "nextcloud",
addOn: "",
result: "nextcloud",
err: false,
},
{
name: "TestGetBillingNameWithAddOn_WhenEmpty_ThenError",
billingName: "",
addOn: "",
result: "",
err: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := getBillingNameWithAddOn(tt.billingName, tt.addOn)
assert.Equal(t, tt.result, actual)
if tt.err {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}

func TestGetExprFromTemplate(t *testing.T) {
tests := []struct {
name string
instances int
result string
}{
{
name: "TestGetExprFromTemplate_WhenInstances1_ThenExprWithInstance",
instances: 1,
result: "vector(1)",
},
{
name: "TestGetExprFromTemplate_WhenInstances0_ThenExprWithInstance",
instances: 0,
result: "vector(0)",
},
{
name: "TestGetExprFromTemplate_WhenInstancesHA_ThenExprWithInstance",
instances: 3,
result: "vector(3)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := getVectorExpression(tt.instances)
assert.Equal(t, tt.result, actual)
})
}
}
Loading

0 comments on commit 8c4e9ec

Please sign in to comment.