Skip to content

Commit

Permalink
add list cube resource quota (#345)
Browse files Browse the repository at this point in the history
* add list cube resource quota

* fix get belong tenants
  • Loading branch information
weilaaa authored Oct 25, 2023
1 parent 5120418 commit 77d029c
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 20 deletions.
19 changes: 19 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# v1.8.10

## BugFix
- add list cube resource quota [#345](https://github.com/kubecube-io/KubeCube/pull/336)

## Dependencies

- hnc v1.0
- nginx-ingress v0.46.0
- helm 3.5
- metrics-server v0.4.1
- elasticsearch 7.8
- kubecube-monitoring 15.4.8
- thanos 3.18.0
- logseer v1.0.0
- logagent v1.0.0
- kubecube-audit v1.2.0
- kubecube-webconsole v1.2.4

# v1.8.9

## BugFix
Expand Down
19 changes: 1 addition & 18 deletions pkg/apiserver/cubeapi/authorization/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,29 +538,12 @@ func (h *handler) getTenantByUser(c *gin.Context) {
userName = c.GetString(constants.UserName)
}

user := user.User{}
err := h.Client.Cache().Get(ctx, types.NamespacedName{Name: userName}, &user)
if err != nil {
response.FailReturn(c, errcode.CustomReturn(http.StatusNotFound, err.Error()))
return
}

tenants := tenantv1.TenantList{}
err = h.Client.Cache().List(ctx, &tenants)
res, err := GetVisibleTenants(ctx, h.Client, userName)
if err != nil {
response.FailReturn(c, errcode.CustomReturn(http.StatusNotFound, err.Error()))
return
}

tenantSet := sets.NewString(user.Status.BelongTenants...)
res := []tenantv1.Tenant{}
for _, t := range tenants.Items {
if !user.Status.PlatformAdmin && !tenantSet.Has(t.Name) {
continue
}
res = append(res, t)
}

sort.SliceStable(res, func(i, j int) bool {
return res[i].CreationTimestamp.Time.After(res[j].CreationTimestamp.Time)
})
Expand Down
61 changes: 61 additions & 0 deletions pkg/apiserver/cubeapi/authorization/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package authorization
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"strings"

"k8s.io/apimachinery/pkg/labels"
Expand Down Expand Up @@ -296,3 +298,62 @@ func isPlatformRole(labels map[string]string) bool {
}
return labels[constants.RoleLabel] == constants.ClusterRolePlatform
}

func GetVisibleTenants(ctx context.Context, cli mgrclient.Client, username string) ([]tenantv1.Tenant, error) {
user := userv1.User{}
err := cli.Cache().Get(ctx, types.NamespacedName{Name: username}, &user)
if err != nil {
return nil, err
}

tenants := tenantv1.TenantList{}
err = cli.Cache().List(ctx, &tenants)
if err != nil {
return nil, err
}

if user.Status.PlatformAdmin {
return tenants.Items, nil
}

tenantSetFromProjects, err := getTenantsOfProjects(ctx, cli, user.Status.BelongProjects)
if err != nil {
return nil, err
}

tenantSet := sets.NewString(user.Status.BelongTenants...)
tenantSet = tenantSet.Union(tenantSetFromProjects)
res := []tenantv1.Tenant{}
for _, t := range tenants.Items {
if !tenantSet.Has(t.Name) {
continue
}
res = append(res, t)
}

return res, nil
}

func getTenantsOfProjects(ctx context.Context, cli mgrclient.Client, projects []string) (sets.String, error) {
tenants := sets.NewString()
for _, project := range projects {
p := tenantv1.Project{}
err := cli.Cache().Get(ctx, types.NamespacedName{Name: project}, &p)
if err != nil {
return nil, err
}
t, ok := getTenantByProject(p)
if ok {
tenants.Insert(t)
}
}
return tenants, nil
}

func getTenantByProject(p tenantv1.Project) (string, bool) {
if p.Labels == nil {
return "", false
}
v, ok := p.Labels[constants.TenantLabel]
return v, ok
}
54 changes: 54 additions & 0 deletions pkg/apiserver/cubeapi/cluster/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cluster

import (
"encoding/base64"
quotav1 "github.com/kubecube-io/kubecube/pkg/apis/quota/v1"
"net/http"
"sort"
"strconv"
Expand Down Expand Up @@ -63,6 +64,7 @@ func (h *handler) AddApisTo(root *gin.Engine) {
r.POST("register", h.registerCluster)
r.POST("add", h.addCluster)
r.POST("nsquota", h.createNsAndQuota)
r.GET("cuberesourcequotas", h.getCubeResourceQuota)
}

type result struct {
Expand Down Expand Up @@ -743,3 +745,55 @@ func (h *handler) createNsAndQuota(c *gin.Context) {

response.SuccessJsonReturn(c, "success")
}

type getCubeResourceQuotaResp struct {
Total int `json:"total"`
Items []cubeResourceQuotaData `json:"items"`
}

type cubeResourceQuotaData struct {
ClusterName string `json:"clusterName"`
ClusterIdentity string `json:"clusterIdentity"`
Tenant string `json:"tenant"`
TenantName string `json:"tenantName"`
CubeResourceQuota *quotav1.CubeResourceQuota `json:"cubeResourceQuota"`
ExclusiveNodeHard map[string]v1.ResourceList `json:"exclusiveNodeHard"`
ClusterState *clusterv1.ClusterState `json:"clusterState"`
}

func (h *handler) getCubeResourceQuota(c *gin.Context) {
ts := c.Query("tenants")
cs := c.Query("clusters")
userName := c.Query("user")
ctx := c.Request.Context()

tenants, clusters := strings.Split(ts, ";"), strings.Split(cs, ";")

if len(userName) == 0 {
userName = c.GetString(constants.UserName)
}

if len(cs) == 0 {
clusters = multicluster.Interface().ListClustersNameByType(multicluster.AllCluster)
}

if len(ts) == 0 {
tenants = nil
}

visibleTenants, visibleTenantsCr, err := getVisibleTenants(ctx, h.Client, userName, tenants)
if err != nil {
response.FailReturn(c, errcode.CustomReturn(http.StatusBadRequest, err.Error()))
return
}

res, err := listCubeResourceQuota(ctx, h.Client, visibleTenants, visibleTenantsCr, clusters)
if err != nil {
response.FailReturn(c, errcode.CustomReturn(http.StatusBadRequest, err.Error()))
return
}

res = sortCubeResourceQuotas(res)

response.SuccessReturn(c, getCubeResourceQuotaResp{Total: len(res), Items: res})
}
155 changes: 155 additions & 0 deletions pkg/apiserver/cubeapi/cluster/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ package cluster
import (
"context"
"fmt"
tenantv1 "github.com/kubecube-io/kubecube/pkg/apis/tenant/v1"
"github.com/kubecube-io/kubecube/pkg/apiserver/cubeapi/authorization"
"github.com/kubecube-io/kubecube/pkg/utils/meta"
"k8s.io/apimachinery/pkg/selection"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -543,3 +547,154 @@ func listHncNsByTenantsFunc(ctx context.Context, tenantList []string) func(cli m
return nsLIst, nil
}
}

func getVisibleTenants(ctx context.Context, cli mgrclient.Client, userName string, tenants []string) ([]string, []tenantv1.Tenant, error) {
visibleTenants, err := authorization.GetVisibleTenants(ctx, cli, userName)
if err != nil {
return nil, nil, err
}
visibleTenantsSet := sets.NewString()
for _, t := range visibleTenants {
visibleTenantsSet.Insert(t.Name)
}
if len(tenants) == 0 {
return visibleTenantsSet.UnsortedList(), visibleTenants, nil
}
queryTenantSet := sets.NewString(tenants...)
if !visibleTenantsSet.IsSuperset(queryTenantSet) {
return nil, nil, fmt.Errorf("query tenants (%v) is not visible for user (%v)", queryTenantSet.UnsortedList(), userName)
}
queryTenantsCr := []tenantv1.Tenant{}
for _, tenant := range visibleTenants {
if queryTenantSet.Has(tenant.Name) {
queryTenantsCr = append(queryTenantsCr, tenant)
}
}
return queryTenantSet.UnsortedList(), queryTenantsCr, nil
}

type clusterDate struct {
cnName string
state *clusterv1.ClusterState
}

func listCubeResourceQuota(ctx context.Context, cli mgrclient.Client, tenants []string, tenantsCr []tenantv1.Tenant, clusters []string) ([]cubeResourceQuotaData, error) {
ls := labels.NewSelector()
r1, err := labels.NewRequirement(constants.ClusterLabel, selection.In, clusters)
if err != nil {
return nil, err
}
r2, err := labels.NewRequirement(constants.TenantLabel, selection.In, tenants)
if err != nil {
return nil, err
}
ls = ls.Add(*r1)
ls = ls.Add(*r2)

list := v1.CubeResourceQuotaList{}
err = cli.Cache().List(ctx, &list, &client.ListOptions{LabelSelector: ls})
if err != nil {
return nil, err
}

// construct cube resource quota map
quotaMap := make(map[string]v1.CubeResourceQuota, len(list.Items))
for _, v := range list.Items {
meta.TrimObjectMeta(&v)
quotaMap[v.Name] = v
}

// construct cluster cn name map
clusterMap := make(map[string]clusterDate, len(clusters))
clusterList := clusterv1.ClusterList{}
err = cli.Cache().List(ctx, &clusterList)
if err != nil {
return nil, err
}
for _, v := range clusterList.Items {
c := clusterDate{state: v.Status.State}
if v.Annotations != nil {
cnName, ok := v.Annotations[constants.CubeCnAnnotation]
if ok {
c.cnName = cnName
}
}
clusterMap[v.Name] = c
}

res := make([]cubeResourceQuotaData, 0, len(tenants)*len(clusters))
for _, tenant := range tenantsCr {
for _, cluster := range clusters {
v := cubeResourceQuotaData{
ClusterIdentity: cluster,
ClusterName: cluster,
Tenant: tenant.Name,
TenantName: tenant.Spec.DisplayName,
CubeResourceQuota: nil,
ExclusiveNodeHard: nil,
}
quotaName := strings.Join([]string{cluster, tenant.Name}, ".")
q, ok := quotaMap[quotaName]
if ok {
v.CubeResourceQuota = &q
}
data, ok := clusterMap[cluster]
if ok {
v.ClusterName = data.cnName
v.ClusterState = data.state
}
clusterCli := clients.Interface().Kubernetes(cluster)
if clusterCli == nil {
return nil, fmt.Errorf("cluster %v not found", cluster)
}
v.ExclusiveNodeHard, err = getExclusiveNodeHard(clusterCli, tenant.Name)
if err != nil {
return nil, err
}
res = append(res, v)
}
}
return res, nil
}

func getExclusiveNodeHard(cli mgrclient.Client, tenant string) (map[string]corev1.ResourceList, error) {
ls, err := labels.Parse(fmt.Sprintf("%v=%v", constants.LabelNodeTenant, tenant))
if err != nil {
return nil, err
}

nodeList := corev1.NodeList{}
err = cli.Cache().List(context.Background(), &nodeList, &client.ListOptions{LabelSelector: ls})
if err != nil {
return nil, err
}
ex := make(map[string]corev1.ResourceList, len(nodeList.Items))
for _, v := range nodeList.Items {
ex[v.Name] = v.Status.Capacity
}
return ex, nil
}

func sortCubeResourceQuotas(qs []cubeResourceQuotaData) []cubeResourceQuotaData {
sort.SliceStable(qs, func(i, j int) bool {
return qs[i].Tenant+qs[i].ClusterName < qs[j].Tenant+qs[j].ClusterName
})

res := []cubeResourceQuotaData{}
bothUnsetted := []cubeResourceQuotaData{}
oneUnsetted := []cubeResourceQuotaData{}

for _, v := range qs {
switch {
case len(v.ExclusiveNodeHard) == 0 && v.CubeResourceQuota == nil:
bothUnsetted = append(bothUnsetted, v)
case len(v.ExclusiveNodeHard) == 0 || v.CubeResourceQuota == nil:
oneUnsetted = append(oneUnsetted, v)
default:
res = append(res, v)
}
}
res = append(res, oneUnsetted...)
res = append(res, bothUnsetted...)
return res
}
5 changes: 3 additions & 2 deletions pkg/multicluster/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
LocalCluster clusterType = iota
PivotCluster
MemberCluster
AllCluster
)

// ManagerImpl instance implement interface,
Expand Down Expand Up @@ -278,7 +279,7 @@ func (m *MultiClustersMgr) ListClustersByType(t clusterType) []*InternalCluster

var clusters []*InternalCluster
for _, v := range m.Clusters {
if v.Type == t {
if v.Type == t || (t == AllCluster && v.Type != LocalCluster) {
clusters = append(clusters, v)
}
}
Expand All @@ -294,7 +295,7 @@ func (m *MultiClustersMgr) ListClustersNameByType(t clusterType) []string {

var clusterNames []string
for _, v := range m.Clusters {
if v.Type == t {
if v.Type == t || (t == AllCluster && v.Type != LocalCluster) {
clusterNames = append(clusterNames, v.Name)
}
}
Expand Down
Loading

0 comments on commit 77d029c

Please sign in to comment.