Skip to content

Commit

Permalink
Merge pull request #74 from natron-io/quotas
Browse files Browse the repository at this point in the history
Quotas redef.
  • Loading branch information
janlauber authored Feb 8, 2022
2 parents 63bd353 + 2f2f8a9 commit 847c274
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 103 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,7 @@ You can send the github code with json body `{"github_code": "..."}` to the `/lo
`INGRESS_COST` - Cost of ingress in your currency *optional* (default: 1.00 for 1 ingress)

### resource quotas
`QUOTA_NAMESPACE_SUFFIX` - The namespace suffix where the config of the tenant configuration takes place *optional* (default: "config" e.g. namespace name: "tenant-config") \
`QUOTA_CPU_LABEL` - The CPU quota label *optional* (default: "natron.io/cpu-quota") \
`QUOTA_MEMORY_LABEL` - The memory quota label *optional* (default: "natron.io/memory-quota")
`QUOTA_STORAGE_LABEl_<storageclass name>` - The storage label of each storage class *optional, multiple allowed* (default: "natron.io/storage-quota-<storageclass name>" renders every storageclass defined in the Storage)

It will get the resource quotas defined in the tenant namespace with the exact name of the tenant.
## labels

### resource quotas
Expand Down
55 changes: 44 additions & 11 deletions controllers/quotaController.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/natron-io/tenant-api/util"
v1 "k8s.io/api/core/v1"
)

// GetCPUQuota returns the CPU quota of a tenant by the label at the tenant config namespace
Expand All @@ -22,13 +23,16 @@ func GetCPUQuota(c *fiber.Ctx) error {
})
}

cpuQuota, err := util.GetRessourceQuota(tenant, util.QUOTA_NAMESPACE_SUFFIX, util.QUOTA_CPU_LABEL)
quota, err := util.GetRessourceQuota(tenant)
if err != nil {
util.ErrorLogger.Printf("%s", err)
return c.Status(500).JSON(fiber.Map{
"message": "Internal Server Error",
})
}

cpuQuota := quota.Spec.Hard.Cpu().MilliValue()

return c.JSON(cpuQuota)
}

Expand All @@ -49,13 +53,16 @@ func GetMemoryQuota(c *fiber.Ctx) error {
})
}

memoryQuota, err := util.GetRessourceQuota(tenant, util.QUOTA_NAMESPACE_SUFFIX, util.QUOTA_MEMORY_LABEL)
quota, err := util.GetRessourceQuota(tenant)
if err != nil {
util.ErrorLogger.Printf("%s", err)
return c.Status(500).JSON(fiber.Map{
"message": "Internal Server Error",
})
}

memoryQuota := quota.Spec.Hard.Memory().Value()

return c.JSON(memoryQuota)
}

Expand All @@ -76,17 +83,43 @@ func GetStorageQuota(c *fiber.Ctx) error {
})
}

storageQuota := make(map[string]float64)
var err error
quota, err := util.GetRessourceQuota(tenant)

if err != nil {
util.ErrorLogger.Printf("%s", err)
return c.Status(500).JSON(fiber.Map{
"message": "Internal Server Error",
})
}

storageClasses, err := util.GetStorageClassesInCluster()
if err != nil {
util.ErrorLogger.Printf("%s", err)
return c.Status(500).JSON(fiber.Map{
"message": "Internal Server Error",
})
}

storageQuota := quota.Spec.Hard

// get first element of storageQuota
storageQuotaMap := make(map[v1.ResourceName]int64)
for key, value := range storageQuota {
storageQuotaMap[key] = value.Value()
}

for key, value := range util.QUOTA_STORAGE_LABEL {
storageQuota[key], err = util.GetRessourceQuota(tenant, util.QUOTA_NAMESPACE_SUFFIX, value)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"message": "Internal Server Error",
})
storageQuotaParsed := make(map[string]int64)
for _, storageClass := range storageClasses {
util.InfoLogger.Printf("%s", storageClass)
storageQuotaString := v1.ResourceName(storageClass + ".storageclass.storage.k8s.io/requests.storage")
if _, ok := storageQuotaMap[storageQuotaString]; ok {
// convert to bytes
storageQuotaParsed[storageClass] = storageQuotaMap[storageQuotaString] * 1024 * 1024
} else {
storageQuotaParsed[storageClass] = 0
}
}

return c.JSON(storageQuota)
// check if storageClass string is in storageQuota
return c.JSON(storageQuotaParsed)
}
2 changes: 1 addition & 1 deletion docs/kubernetes/clusterrolebinding.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ subjects:
name: tenant-api
roleRef:
kind: ClusterRole #this must be Role or ClusterRole
name: view
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
14 changes: 14 additions & 0 deletions docs/kubernetes/resourcequota.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: ResourceQuota
metadata:
name: netrics
spec:
hard:
cpu: "10"
memory: 20Gi
longhorn.storageclass.storage.k8s.io/requests.storage: 0Gi
longhorn-hdd-backup.storageclass.storage.k8s.io/requests.storage: 0Gi
longhorn-hdd-backup-long.storageclass.storage.k8s.io/requests.storage: 0Gi
longhorn-ssd-backup.storageclass.storage.k8s.io/requests.storage: 0Gi
longhorn-ssd-backup-long.storageclass.storage.k8s.io/requests.storage: 50Gi
standard.storageclass.storage.k8s.io/requests.storage: 50Gi
35 changes: 15 additions & 20 deletions util/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package util
import (
"context"
"strconv"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
Expand All @@ -19,7 +20,7 @@ func GetPodsByTenant(tenants []string) (map[string][]string, error) {
// get namespace with same name as tenant and get pods
for _, tenant := range tenants {
pods, err := Clientset.CoreV1().Pods(tenant).List(context.TODO(), metav1.ListOptions{})
if err != nil {
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, err
}

Expand All @@ -39,7 +40,7 @@ func GetCPURequestsSumByTenant(tenants []string) (map[string]int64, error) {
for _, tenant := range tenants {
pods, err := Clientset.CoreV1().Pods(tenant).List(context.TODO(), metav1.ListOptions{})

if err != nil {
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, err
}

Expand Down Expand Up @@ -69,7 +70,7 @@ func GetMemoryRequestsSumByTenant(tenants []string) (map[string]int64, error) {
tenantMemoryRequests := make(map[string]int64)
for _, tenant := range tenants {
pods, err := Clientset.CoreV1().Pods(tenant).List(context.TODO(), metav1.ListOptions{})
if err != nil {
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, err
}

Expand Down Expand Up @@ -100,7 +101,7 @@ func GetStorageRequestsSumByTenant(tenants []string) (map[string]map[string]int6
for _, tenant := range tenants {
pvcList, err := Clientset.CoreV1().PersistentVolumeClaims(tenant).List(context.TODO(), metav1.ListOptions{})

if err != nil {
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, err
}

Expand Down Expand Up @@ -138,7 +139,7 @@ func GetIngressRequestsSumByTenant(tenants []string) (map[string][]string, error
// get ingress for each namespace in the tenant and add it to the map of ingress for the tenant
ingressList, err := Clientset.NetworkingV1().Ingresses(tenant).List(context.TODO(), metav1.ListOptions{})

if err != nil {
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, err
}

Expand All @@ -164,23 +165,17 @@ func GetIngressRequestsSumByTenant(tenants []string) (map[string][]string, error
return tenantsIngress, nil
}

// GetRessourceQuota returns the resource quota for the given tenant and label set in the config namespace
func GetRessourceQuota(tenant string, namespace_suffix string, label string) (float64, error) {
// get the namespace with tenant-namespace_suffix and get the label value
namespace, err := Clientset.CoreV1().Namespaces().Get(context.TODO(), tenant, metav1.GetOptions{})
if err != nil {
return 0, err
}
func GetStorageClassesInCluster() ([]string, error) {
storageClasses := make([]string, 0)
scList, err := Clientset.StorageV1().StorageClasses().List(context.TODO(), metav1.ListOptions{})

// get the cpu quota from the label
quota := namespace.Labels[label]
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, err
}

// convert to float64
quotaFloat, err := strconv.ParseFloat(quota, 64)
if err != nil || quotaFloat < 0 {
WarningLogger.Printf("CPU quota value %s is not valid for pod %s with label %s", quota, namespace.Name, label)
return 0, err
for _, sc := range scList.Items {
storageClasses = append(storageClasses, sc.Name)
}

return quotaFloat, nil
return storageClasses, nil
}
61 changes: 0 additions & 61 deletions util/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ func LoadEnv() error {
}

// get every env variable starting with STORAGE_COST_ and parse it to STORAGE_COST with the storage class name after STORAGE_COST_ as key
storageClasses := []string{}
tempStorageCost := make(map[string]map[string]float64)
for _, env := range os.Environ() {
if strings.HasPrefix(env, "STORAGE_COST_") {
Expand All @@ -126,7 +125,6 @@ func LoadEnv() error {
// add to tempStorageCost
tempStorageCost[key[2]] = map[string]float64{"cost": value}
InfoLogger.Printf("storage class %s set to cost value: %f", key[2], value)
storageClasses = append(storageClasses, key[2])
}
}
STORAGE_COST = tempStorageCost
Expand All @@ -147,64 +145,5 @@ func LoadEnv() error {
InfoLogger.Printf("INGRESS_COST set to: %f", INGRESS_COST)
}

if QUOTA_CPU_LABEL = os.Getenv("QUOTA_CPU_LABEL"); QUOTA_CPU_LABEL == "" {
WarningLogger.Println("QUOTA_CPU_LABEL is not set")
QUOTA_CPU_LABEL = "natron.io/cpu-quota"
InfoLogger.Printf("QUOTA_CPU_LABEL set using default: %s", QUOTA_CPU_LABEL)
} else {
InfoLogger.Printf("QUOTA_CPU_LABEL set using env: %s", QUOTA_CPU_LABEL)
}

if QUOTA_MEMORY_LABEL = os.Getenv("QUOTA_MEMORY_LABEL"); QUOTA_MEMORY_LABEL == "" {
WarningLogger.Println("QUOTA_MEMORY_LABEL is not set")
QUOTA_MEMORY_LABEL = "natron.io/memory-quota"
InfoLogger.Printf("QUOTA_MEMORY_LABEL set using default: %s", QUOTA_MEMORY_LABEL)
} else {
InfoLogger.Printf("QUOTA_MEMORY_LABEL set using env: %s", QUOTA_MEMORY_LABEL)
}

if QUOTA_NAMESPACE_SUFFIX = os.Getenv("QUOTA_NAMESPACE_SUFFIX"); QUOTA_NAMESPACE_SUFFIX == "" {
WarningLogger.Println("QUOTA_NAMESPACE_SUFFIX is not set")
QUOTA_NAMESPACE_SUFFIX = "config"
InfoLogger.Printf("QUOTA_NAMESPACE_SUFFIX set using default: %s", QUOTA_NAMESPACE_SUFFIX)
} else {
InfoLogger.Printf("QUOTA_NAMESPACE_SUFFIX set using env: %s", QUOTA_NAMESPACE_SUFFIX)
}

// for each storageclass get the quota label
QUOTA_STORAGE_LABEL = make(map[string]string)
// get storage class names from env
for _, env := range os.Environ() {
if strings.HasPrefix(env, "QUOTA_STORAGE_LABEL_") {
// split env variable to key and value
keyValue := strings.Split(env, "=")
// split key to storage class name and cost
key := strings.Split(keyValue[0], "_")
// add to tempStorageCost

// check if storage class already exists in storageClasses
for _, storageClass := range storageClasses {
if storageClass == key[3] {
break
}
}
storageClasses = append(storageClasses, key[3])
}
}

for _, storageClass := range storageClasses {
label := os.Getenv("QUOTA_STORAGE_LABEL_" + storageClass)
if label == "" {
WarningLogger.Printf("QUOTA_STORAGE_LABEL_%s is not set", storageClass)
label = "natron.io/storage-quota-" + storageClass
InfoLogger.Printf("QUOTA_STORAGE_LABEL_%s set using default: %s", storageClass, label)
// add to QUOTA_STORAGE_LABEL
QUOTA_STORAGE_LABEL[storageClass] = label
} else {
InfoLogger.Printf("QUOTA_STORAGE_LABEL_%s set using env: %s", storageClass, label)
QUOTA_STORAGE_LABEL[storageClass] = label
}
}

return nil
}
23 changes: 18 additions & 5 deletions util/quota.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
package util

var (
QUOTA_NAMESPACE_SUFFIX string
QUOTA_CPU_LABEL string
QUOTA_MEMORY_LABEL string
QUOTA_STORAGE_LABEL map[string]string
import (
"context"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GetRessourceQuota returns the resource quota for the given tenant and label set in the config namespace
func GetRessourceQuota(tenant string) (v1.ResourceQuota, error) {
// get resource quota in namespace "test-tenant-config"

// get resource quota from namespace
quota, err := Clientset.CoreV1().ResourceQuotas(tenant).Get(context.TODO(), tenant, metav1.GetOptions{})
if err != nil {
return v1.ResourceQuota{}, err
}

return *quota, nil
}

0 comments on commit 847c274

Please sign in to comment.