Skip to content

Commit

Permalink
Merge pull request #77 from natron-io/feature/#70
Browse files Browse the repository at this point in the history
implement exclusion and ingress calc via domain
  • Loading branch information
janlauber authored Feb 9, 2022
2 parents bfd96f9 + e91d66b commit d87bd78
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ docs/kubernetes/pvc.yaml
docs/kubernetes/oingress.yaml
docs/kubernetes/ns.yaml
docs/kubernetes/ns-config.yaml
.DS_Store
docs/kubernetes/kubeconfig.yaml
.DS_Store
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ You can send the github code with json body `{"github_code": "..."}` to the `/lo
`CPU_COST` - Cost of a CPU in your currency *optional* (default: 1.00 for 1 CPU) \
`MEMORY_COST` - Cost of a memory in your currency *optional* (default: 1.00 for 1 GB) \
`STORAGE_COST_<storageclass name>` - Cost of your storage classes in your currency **required, multiple allowed** (default: 1.00 for 1 GB) \
`INGRESS_COST` - Cost of ingress in your currency *optional* (default: 1.00 for 1 ingress)
`INGRESS_COST` - Cost of ingress in your currency *optional* (default: 1.00 for 1 ingress) \
`INGRESS_COST_PER_DOMAIN` - Calculates only ingress per domain.tld format *optional* (default: false) \
`EXCLUDE_INGRESS_VCLUSTER` - Excludes the vcluster ingress resource to expose the vcluster Kubernetes API. Name of the ingress must contain the string "vcluster" *optional* (default: false)


### resource quotas
It will get the resource quotas defined in the tenant namespace with the exact name of the tenant.
Expand Down
28 changes: 22 additions & 6 deletions controllers/costController.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,40 @@ func GetIngressCostSum(c *fiber.Ctx) error {
}

// create a map for each tenant with a added ingress requests
tenantIngressRequests, err := util.GetIngressRequestsSumByTenant(tenants)
tenantsIngressRequests, err := util.GetIngressRequestsSumByTenant(tenants)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"message": "Internal Server Error",
})
}

// create a map for each tenant with a added ingress costs only if cost is not 0
tenantIngressCosts := make(map[string]float64)

if !util.INGRESS_COST_PER_DOMAIN {
tenantsIngressCosts := make(map[string]float64)
for _, tenant := range tenants {
if tenantsIngressRequests[tenant] != nil {
tenantsIngressCosts[tenant] = util.GetIngressCost(len(tenantsIngressRequests[tenant]))
}
}

if tenant == "" {
return c.JSON(tenantsIngressCosts)
} else {
return c.JSON(tenantsIngressCosts[tenant])
}
}

tenantsIngressCostsPerDomain := make(map[string]float64)
for _, tenant := range tenants {
if len(tenantIngressRequests[tenant]) != 0 {
tenantIngressCosts[tenant] = util.GetIngressCost(len(tenantIngressRequests[tenant]))
if tenantsIngressRequests[tenant] != nil {
tenantsIngressCostsPerDomain[tenant] = util.GetIngressCostByDomain(tenantsIngressRequests[tenant])
}
}

if tenant == "" {
return c.JSON(tenantIngressCosts)
return c.JSON(tenantsIngressCostsPerDomain)
} else {
return c.JSON(tenantIngressCosts[tenant])
return c.JSON(tenantsIngressCostsPerDomain[tenant])
}
}
61 changes: 53 additions & 8 deletions docs/kubernetes/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ metadata:
name: tenant-api
labels:
app: tenant-api
natron.io/tenant: netrics
natron.io/discount: "0.5"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-natron
#natron.io/tenant: netrics
# natron.io/discount: "0.5"
spec:
rules:
- host: api.example.com
Expand All @@ -20,7 +18,54 @@ spec:
name: tenant-api
port:
number: 8000
tls:
- hosts:
- api.example.com
secretName: api-natron-io-cert
- host: api2.example.com
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: tenant-api
port:
number: 8000
- host: api2.example2.com
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: tenant-api
port:
number: 8000
- host: api.example3.com
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: tenant-api
port:
number: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vcluster
labels:
app: vcluster
#natron.io/tenant: netrics
# natron.io/discount: "0.5"
spec:
rules:
- host: tenant.natron.io
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: tenant-api
port:
number: 8000
12 changes: 6 additions & 6 deletions tenant-api.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ func init() {
util.InitLoggers()
util.Status = "Running"

// load util config envs
if err := util.LoadEnv(); err != nil {
util.ErrorLogger.Println("Error loading env variables")
os.Exit(1)
}

// creates the in-cluster config with ratelimiter to qps: 20 and burst: 50
config, err := rest.InClusterConfig()
if err != nil {
Expand All @@ -58,6 +52,12 @@ func init() {
util.ErrorLogger.Printf("Error creating clientset: %v", err)
os.Exit(1)
}

// load util config envs
if err := util.LoadEnv(); err != nil {
util.ErrorLogger.Println("Error loading env variables")
os.Exit(1)
}
}

func main() {
Expand Down
40 changes: 38 additions & 2 deletions util/costs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package util

import (
"fmt"
"strings"
)

var (
CPU_COST float64
MEMORY_COST float64
STORAGE_COST map[string]map[string]float64
INGRESS_COST float64
INGRESS_COST_PER_DOMAIN bool
EXCLUDE_INGRESS_VCLUSTER bool
CPU_DISCOUNT_PERCENT float64
MEMORY_DISCOUNT_PERCENT float64
STORAGE_DISCOUNT_PERCENT float64
Expand Down Expand Up @@ -38,6 +41,39 @@ func GetStorageCost(storageClass string, size float64) (float64, error) {
}

// GetIngressCost returns the cost of the provided Ingress
func GetIngressCost(ingress int) float64 {
return (INGRESS_COST * float64(ingress)) * (1 - INGRESS_DISCOUNT_PERCENT)
func GetIngressCostByDomain(hostnameStrings []string) float64 {

var tenantIngressCostsPerDomainSum float64

// define a set of domains
domains := make(map[string]bool)

for _, host := range hostnameStrings {
// split string with .
hostnameParts := strings.Split(host, ".")
// get the 2 last parts of the hostname
var domain string
if len(hostnameParts) > 1 {
domain = hostnameParts[len(hostnameParts)-2] + "." + hostnameParts[len(hostnameParts)-1]
} else {
domain = ""
}
// add the domain to the tenantIngressCosts map
if domain != "" {
domains[domain] = true
} else {
ErrorLogger.Printf("domain is not valid for hostname %s", host)
}
}

// calculate the cost * count of domains
for range domains {
tenantIngressCostsPerDomainSum += INGRESS_COST * (1 - INGRESS_DISCOUNT_PERCENT)
}

return tenantIngressCostsPerDomainSum
}

func GetIngressCost(ingressCount int) float64 {
return INGRESS_COST * float64(ingressCount) * (1 - INGRESS_DISCOUNT_PERCENT)
}
15 changes: 11 additions & 4 deletions util/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
)

var (
Clientset *kubernetes.Clientset
DISCOUNT_LABEL string
Clientset *kubernetes.Clientset
DISCOUNT_LABEL string
EXCLUDE_STRINGS []string
)

// GetPodsByTenant returns a map of pods for each tenant
Expand All @@ -27,7 +28,7 @@ func GetPodsByTenant(tenants []string) (map[string][]string, error) {
// for each pod add it to the list of pods for the namespace
tenantPods[tenant] = make([]string, 0)
for _, pod := range pods.Items {
tenantPods[tenant] = append(tenantPods[tenant], pod.Name)
tenantPods[tenant] = append(tenantPods[tenant], strings.Split(pod.Name, "-x-")[0])
}
}

Expand Down Expand Up @@ -157,8 +158,14 @@ func GetIngressRequestsSumByTenant(tenants []string) (map[string][]string, error

INGRESS_DISCOUNT_PERCENT = discountFloat

if strings.Contains(ingress.Name, "vcluster") && EXCLUDE_INGRESS_VCLUSTER {
continue
}

// apend ingress hostname to the list of ingress for the tenant
tenantsIngress[tenant] = append(tenantsIngress[tenant], ingress.Name)
for _, rule := range ingress.Spec.Rules {
tenantsIngress[tenant] = append(tenantsIngress[tenant], rule.Host)
}
}
}

Expand Down
53 changes: 45 additions & 8 deletions util/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,30 @@ func LoadEnv() error {
InfoLogger.Printf("MEMORY_COST set using env: %f", MEMORY_COST)
}

if INGRESS_COST, err = strconv.ParseFloat(os.Getenv("INGRESS_COST"), 64); INGRESS_COST == 0 || err != nil {
WarningLogger.Println("INGRESS_COST is not set or invalid float value")
INGRESS_COST = 1.00
InfoLogger.Printf("INGRESS_COST set using default: %f", INGRESS_COST)
} else {
InfoLogger.Printf("INGRESS_COST set to: %f", INGRESS_COST)
}

if INGRESS_COST_PER_DOMAIN, err = strconv.ParseBool(os.Getenv("INGRESS_COST_PER_DOMAIN")); !INGRESS_COST_PER_DOMAIN || err != nil {
WarningLogger.Println("INGRESS_COST_PER_DOMAIN is not set or invalid bool value")
INGRESS_COST_PER_DOMAIN = false
InfoLogger.Printf("INGRESS_COST_PER_DOMAIN set using default: %t", INGRESS_COST_PER_DOMAIN)
} else {
InfoLogger.Printf("INGRESS_COST_PER_DOMAIN set using env: %t", INGRESS_COST_PER_DOMAIN)
}

if EXCLUDE_INGRESS_VCLUSTER, err = strconv.ParseBool(os.Getenv("EXCLUDE_INGRESS_VCLUSTER")); !EXCLUDE_INGRESS_VCLUSTER || err != nil {
WarningLogger.Println("EXCLUDE_INGRESS_VCLUSTER is not set or invalid bool value")
EXCLUDE_INGRESS_VCLUSTER = false
InfoLogger.Printf("EXCLUDE_INGRESS_VCLUSTER set using default: %t", EXCLUDE_INGRESS_VCLUSTER)
} else {
InfoLogger.Printf("EXCLUDE_INGRESS_VCLUSTER set using env: %t", EXCLUDE_INGRESS_VCLUSTER)
}

if SLACK_TOKEN = os.Getenv("SLACK_TOKEN"); SLACK_TOKEN == "" {
WarningLogger.Println("SLACK_TOKEN is not set")
SLACK_TOKEN = ""
Expand All @@ -106,6 +130,9 @@ func LoadEnv() error {
InfoLogger.Printf("SLACK_URL set using env: %s", SlackURL)
}

// ======================== //
// StorageClasses //
// ======================== //
// get every env variable starting with STORAGE_COST_ and parse it to STORAGE_COST with the storage class name after STORAGE_COST_ as key
tempStorageCost := make(map[string]map[string]float64)
for _, env := range os.Environ() {
Expand All @@ -129,6 +156,24 @@ func LoadEnv() error {
}
STORAGE_COST = tempStorageCost

storageClassesInCluster, err := GetStorageClassesInCluster()
if err != nil {
err = errors.New("cannot get storage classes in cluster")
ErrorLogger.Println(err)
Status = "Error: " + err.Error()
os.Exit(1)
}

// check if every storage class in cluster is in STORAGE_COST
for _, storageClass := range storageClassesInCluster {
if _, ok := STORAGE_COST[storageClass]; !ok {
err = errors.New("Storage class " + storageClass + " is not set")
ErrorLogger.Println(err)
Status = "Error: " + err.Error()
os.Exit(1)
}
}

if STORAGE_COST == nil {
WarningLogger.Println("STORAGE_COST is not set")
STORAGE_COST = map[string]map[string]float64{
Expand All @@ -137,13 +182,5 @@ func LoadEnv() error {
InfoLogger.Printf("cost for storage class default set using default: %f", STORAGE_COST["default"]["cost"])
}

if INGRESS_COST, err = strconv.ParseFloat(os.Getenv("INGRESS_COST"), 64); INGRESS_COST == 0 || err != nil {
WarningLogger.Println("INGRESS_COST is not set or invalid float value")
INGRESS_COST = 1.00
InfoLogger.Printf("INGRESS_COST set using default: %f", INGRESS_COST)
} else {
InfoLogger.Printf("INGRESS_COST set to: %f", INGRESS_COST)
}

return nil
}

0 comments on commit d87bd78

Please sign in to comment.