Skip to content

Commit

Permalink
Add service for cost-tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
marcus-sva committed Jan 7, 2025
1 parent 9cafa53 commit f45c766
Show file tree
Hide file tree
Showing 52 changed files with 4,742 additions and 3,048 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ jobs:
path: ./v3/services/conversionsvc
image: conversion-service
secrets: inherit
build-cost-service:
uses: ./.github/workflows/build.yaml
with:
path: ./v3/services/costsvc
image: cost-service
secrets: inherit
build-course-service:
uses: ./.github/workflows/build.yaml
with:
Expand Down
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ use (
./v3/services/scenariosvc
./v3/services/coursesvc
./v3/services/terraformsvc
./v3/services/costsvc
)
2 changes: 2 additions & 0 deletions v3/pkg/apis/hobbyfarm.io/v1/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&ScopeList{},
&OneTimeAccessCode{},
&OneTimeAccessCodeList{},
&Cost{},
&CostList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
Expand Down
104 changes: 94 additions & 10 deletions v3/pkg/apis/hobbyfarm.io/v1/types.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package v1

import (
"fmt"
"github.com/hobbyfarm/gargantua/v3/pkg/property"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"math"
"time"
)

type VmStatus string
Expand Down Expand Up @@ -269,15 +272,15 @@ type ScenarioList struct {
}

type ScenarioSpec struct {
Name string `json:"name"`
Description string `json:"description"`
Steps []ScenarioStep `json:"steps"`
Categories []string `json:"categories"`
Tags []string `json:"tags"`
VirtualMachines []map[string]string `json:"virtualmachines"`
KeepAliveDuration string `json:"keepalive_duration"`
PauseDuration string `json:"pause_duration"`
Pauseable bool `json:"pauseable"`
Name string `json:"name"`
Description string `json:"description"`
Steps []ScenarioStep `json:"steps"`
Categories []string `json:"categories"`
Tags []string `json:"tags"`
VirtualMachines []map[string]string `json:"virtualmachines"`
KeepAliveDuration string `json:"keepalive_duration"`
PauseDuration string `json:"pause_duration"`
Pauseable bool `json:"pauseable"`
Tasks []VirtualMachineTasks `json:"vm_tasks"`
}

Expand All @@ -296,7 +299,7 @@ type Task struct {
Command string `json:"command"`
ExpectedOutputValue string `json:"expected_output_value"`
ExpectedReturnCode int `json:"expected_return_code"`
ReturnType string `json:"return_type"`
ReturnType string `json:"return_type"`
}

// +genclient
Expand Down Expand Up @@ -570,3 +573,84 @@ type ScopeList struct {

Items []Scope `json:"items"`
}

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type Cost struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CostSpec `json:"spec"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type CostList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Cost `json:"items"`
}

type CostSpec struct {
CostGroup string `json:"cost_group"`
Resources []CostResource `json:"resources"`
}

type CostResource struct {
Id string `json:"id"` // id of the resource
Kind string `json:"kind"` // name like VirtualMachine
BasePrice uint64 `json:"base_price"`
TimeUnit TimeUnit `json:"time_unit"`
CreationUnixTimestamp int64 `json:"creation_unix_timestamp"` // unix timestamp in seconds
DeletionUnixTimestamp int64 `json:"deletion_unix_timestamp,omitempty"` // unix timestamp in seconds
}

type TimeUnit string

const (
TimeUnitSeconds TimeUnit = "seconds"
TimeUnitMinutes TimeUnit = "minutes"
TimeUnitHours TimeUnit = "hours"
)

func ParseTimeUnit(s string) (TimeUnit, error) {
switch s {
case "seconds", "second", "sec", "s":
return TimeUnitSeconds, nil
case "minutes", "minute", "min", "m":
return TimeUnitMinutes, nil
case "hours", "hour", "h":
return TimeUnitHours, nil
default:
return TimeUnitSeconds, fmt.Errorf("%s is not a valid time unit", s)
}
}

func (cr *CostResource) CalcCost(duration time.Duration) uint64 {
var durationInTimeUnit uint64

switch cr.TimeUnit {
case TimeUnitSeconds:
durationInTimeUnit = uint64(math.Ceil(duration.Seconds()))
case TimeUnitMinutes:
durationInTimeUnit = uint64(math.Ceil(duration.Minutes()))
case TimeUnitHours:
durationInTimeUnit = uint64(math.Ceil(duration.Hours()))
default:
durationInTimeUnit = 0
}

return durationInTimeUnit * cr.BasePrice
}

func (cr *CostResource) Duration(defaultDeletion time.Time) time.Duration {
creation := time.Unix(cr.CreationUnixTimestamp, 0)

deletion := defaultDeletion
if cr.DeletionUnixTimestamp != 0 {
deletion = time.Unix(cr.DeletionUnixTimestamp, 0)
}

return deletion.Sub(creation)
}
101 changes: 101 additions & 0 deletions v3/pkg/apis/hobbyfarm.io/v1/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package v1

import (
"testing"
"time"
)

func TestCostResource_Duration(t *testing.T) {
defaultDeletion := time.Unix(10, 0)

tests := []struct {
name string
input CostResource
want time.Duration
}{
{
name: "deletion",
input: CostResource{
CreationUnixTimestamp: 0,
DeletionUnixTimestamp: 10,
},
want: 10 * time.Second,
},
{
name: "no deletion",
input: CostResource{
CreationUnixTimestamp: 0,
},
want: 10 * time.Second,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.input.Duration(defaultDeletion); got != tt.want {
t.Errorf("Duration() = %v, want %v", got, tt.want)
}
})
}
}

func TestCostResource_CalcCost(t *testing.T) {
tests := []struct {
name string
input CostResource
duration time.Duration
want uint64
}{
{
name: "seconds",
input: CostResource{
BasePrice: 1,
TimeUnit: TimeUnitSeconds,
},
duration: 10 * time.Second,
want: 10,
},
{
name: "minutes",
input: CostResource{
BasePrice: 2,
TimeUnit: TimeUnitMinutes,
},
duration: 60 * time.Second,
want: 2,
},
{
name: "minutes and always round up",
input: CostResource{
BasePrice: 2,
TimeUnit: TimeUnitMinutes,
},
duration: 61 * time.Second,
want: 4,
},
{
name: "hours",
input: CostResource{
BasePrice: 2,
TimeUnit: TimeUnitHours,
},
duration: 1 * time.Hour,
want: 2,
},
{
name: "hours and always round up",
input: CostResource{
BasePrice: 2,
TimeUnit: TimeUnitHours,
},
duration: 61 * time.Minute,
want: 4,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.input.CalcCost(tt.duration); got != tt.want {
t.Errorf("CalcCost() = %v, want %v", got, tt.want)
}
})
}
}
97 changes: 97 additions & 0 deletions v3/pkg/apis/hobbyfarm.io/v1/zz_generated.deepcopy.go

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

Loading

0 comments on commit f45c766

Please sign in to comment.