diff --git a/README.md b/README.md index 31fa6af..eef9246 100644 --- a/README.md +++ b/README.md @@ -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_` - The storage label of each storage class *optional, multiple allowed* (default: "natron.io/storage-quota-" 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 diff --git a/controllers/quotaController.go b/controllers/quotaController.go index db04cda..c200cf9 100644 --- a/controllers/quotaController.go +++ b/controllers/quotaController.go @@ -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 @@ -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) } @@ -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) } @@ -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) } diff --git a/docs/kubernetes/clusterrolebinding.yaml b/docs/kubernetes/clusterrolebinding.yaml index 2d0d259..fb3d0a5 100644 --- a/docs/kubernetes/clusterrolebinding.yaml +++ b/docs/kubernetes/clusterrolebinding.yaml @@ -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 diff --git a/docs/kubernetes/resourcequota.yaml b/docs/kubernetes/resourcequota.yaml new file mode 100644 index 0000000..69166a6 --- /dev/null +++ b/docs/kubernetes/resourcequota.yaml @@ -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 \ No newline at end of file diff --git a/util/k8s.go b/util/k8s.go index 4318b2d..0e1fe2c 100644 --- a/util/k8s.go +++ b/util/k8s.go @@ -3,6 +3,7 @@ package util import ( "context" "strconv" + "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -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 } @@ -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 } @@ -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 } @@ -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 } @@ -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 } @@ -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 } diff --git a/util/os.go b/util/os.go index 4bc843a..fb69b04 100644 --- a/util/os.go +++ b/util/os.go @@ -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_") { @@ -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 @@ -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 } diff --git a/util/quota.go b/util/quota.go index abda7d1..20cf2eb 100644 --- a/util/quota.go +++ b/util/quota.go @@ -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 +}