Permalink
Browse files

Handle case when quota limit fields get added/removed

  • Loading branch information...
alena1108 committed Sep 17, 2018
1 parent 4fe4667 commit da4d0f791c2a1d0463a76fcb3f3eda1485c31521
@@ -5,14 +5,17 @@ import (
"fmt"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/rancher/norman/api/access"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/values"
"github.com/rancher/rancher/pkg/resourcequota"
clusterchema "github.com/rancher/types/apis/cluster.cattle.io/v3/schema"
"github.com/rancher/types/apis/management.cattle.io/v3"
mgmtschema "github.com/rancher/types/apis/management.cattle.io/v3/schema"
clusterclient "github.com/rancher/types/client/cluster/v3"
mgmtclient "github.com/rancher/types/client/management/v3"
"github.com/rancher/types/config"
"k8s.io/apimachinery/pkg/labels"
@@ -151,7 +154,8 @@ func (s *projectStore) validateResourceQuota(apiContext *types.APIContext, data
return isQuotaFit(apiContext, nsQuotaLimit, projectQuotaLimit, id)
}
func isQuotaFit(apiContext *types.APIContext, nsQuotaLimit *v3.ResourceQuotaLimit, projectQuotaLimit *v3.ResourceQuotaLimit, id string) error {
func isQuotaFit(apiContext *types.APIContext, nsQuotaLimit *v3.ResourceQuotaLimit,
projectQuotaLimit *v3.ResourceQuotaLimit, id string) error {
// check that namespace default quota is within project quota
isFit, msg, err := resourcequota.IsQuotaFit(nsQuotaLimit, []*v3.ResourceQuotaLimit{}, projectQuotaLimit)
if err != nil {
@@ -171,12 +175,51 @@ func isQuotaFit(apiContext *types.APIContext, nsQuotaLimit *v3.ResourceQuotaLimi
return err
}
if project.ResourceQuota == nil || project.ResourceQuota.UsedLimit == nil {
if project.ResourceQuota == nil {
return nil
}
// check if fields were added or removed
// and update project's namespaces accordingly
defaultQuotaLimitMap, err := convert.EncodeToMap(nsQuotaLimit)
if err != nil {
return err
}
usedQuotaLimitMap := map[string]interface{}{}
if project.ResourceQuota.UsedLimit != nil {
usedQuotaLimitMap, err = convert.EncodeToMap(project.ResourceQuota.UsedLimit)
if err != nil {
return err
}
}
limitToAdd := map[string]interface{}{}
limitToRemove := map[string]interface{}{}
for key, value := range defaultQuotaLimitMap {
if _, ok := usedQuotaLimitMap[key]; !ok {
limitToAdd[key] = value
}
}
for key, value := range usedQuotaLimitMap {
if _, ok := defaultQuotaLimitMap[key]; !ok {
limitToRemove[key] = value
}
}
// check that used quota is not bigger than the project quota
usedQuotaLimit, err := limitToLimit(project.ResourceQuota.UsedLimit)
for key := range limitToRemove {
delete(usedQuotaLimitMap, key)
}
var usedLimitToCheck mgmtclient.ResourceQuotaLimit
err = convert.ToObj(usedQuotaLimitMap, &usedLimitToCheck)
if err != nil {
return err
}
usedQuotaLimit, err := limitToLimit(&usedLimitToCheck)
if err != nil {
return err
}
@@ -189,6 +232,47 @@ func isQuotaFit(apiContext *types.APIContext, nsQuotaLimit *v3.ResourceQuotaLimi
msg))
}
if len(limitToAdd) == 0 && len(limitToRemove) == 0 {
return nil
}
// check if default quota is enough to set on namespaces
mu := resourcequota.GetProjectLock(id)
mu.Lock()
defer mu.Unlock()
var namespaces []clusterclient.Namespace
options := &types.QueryOptions{
Conditions: []*types.QueryCondition{
types.NewConditionFromString("projectId", types.ModifierEQ, id),
},
}
if err := access.List(apiContext, &clusterchema.Version, clusterclient.NamespaceType, options, &namespaces); err != nil {
return err
}
var nsLimits []*v3.ResourceQuotaLimit
toAppend := &mgmtclient.ResourceQuotaLimit{}
if err := mapstructure.Decode(limitToAdd, toAppend); err != nil {
return err
}
converted, err := limitToLimit(toAppend)
if err != nil {
return err
}
for i := 0; i < len(namespaces); i++ {
nsLimits = append(nsLimits, converted)
}
isFit, msg, err = resourcequota.IsQuotaFit(&v3.ResourceQuotaLimit{}, nsLimits, projectQuotaLimit)
if err != nil {
return err
}
if !isFit {
return httperror.NewFieldAPIError(httperror.MaxLimitExceeded, namespaceQuotaField,
fmt.Sprintf("exceeds project limit on fields %s when applied to all namespaces in a project",
msg))
}
return nil
}
@@ -131,13 +131,15 @@ func (p *Store) validateResourceQuota(apiContext *types.APIContext, schema *type
var nsLimits []*v3.ResourceQuotaLimit
var namespaces []clusterclient.Namespace
if err := access.List(apiContext, &schema.Version, clusterclient.NamespaceType, &types.QueryOptions{}, &namespaces); err != nil {
options := &types.QueryOptions{
Conditions: []*types.QueryCondition{
types.NewConditionFromString("projectId", types.ModifierEQ, convert.ToString(data["projectId"])),
},
}
if err := access.List(apiContext, &schema.Version, clusterclient.NamespaceType, options, &namespaces); err != nil {
return err
}
for _, n := range namespaces {
if n.ProjectID != data["projectId"] {
continue
}
if n.ID == id {
continue
}
@@ -6,6 +6,8 @@ import (
"reflect"
"time"
"github.com/mitchellh/mapstructure"
"github.com/rancher/norman/types/convert"
namespaceutil "github.com/rancher/rancher/pkg/namespace"
validate "github.com/rancher/rancher/pkg/resourcequota"
"github.com/rancher/types/apis/core/v1"
@@ -66,6 +68,11 @@ func (c *SyncController) CreateResourceQuota(ns *corev1.Namespace) (*corev1.Name
}
}
quotaToUpdate, err := c.getResourceQuotaToUpdate(ns)
if err != nil {
return ns, err
}
operation := "none"
if existing == nil {
if quotaSpec != nil {
@@ -74,7 +81,7 @@ func (c *SyncController) CreateResourceQuota(ns *corev1.Namespace) (*corev1.Name
} else {
if quotaSpec == nil {
operation = "delete"
} else if !reflect.DeepEqual(existing.Spec.Hard, quotaSpec.Hard) {
} else if quotaToUpdate != "" || !reflect.DeepEqual(existing.Spec.Hard, quotaSpec.Hard) {
operation = "update"
}
}
@@ -83,7 +90,7 @@ func (c *SyncController) CreateResourceQuota(ns *corev1.Namespace) (*corev1.Name
var isFit bool
switch operation {
case "create":
isFit, updated, err = c.validateAndSetNamespaceQuota(ns)
isFit, updated, err = c.validateAndSetNamespaceQuota(ns, quotaToUpdate)
if err != nil {
return updated, err
}
@@ -96,7 +103,7 @@ func (c *SyncController) CreateResourceQuota(ns *corev1.Namespace) (*corev1.Name
}
err = c.createDefaultResourceQuota(ns, quotaSpec)
case "update":
isFit, updated, err = c.validateAndSetNamespaceQuota(ns)
isFit, updated, err = c.validateAndSetNamespaceQuota(ns, quotaToUpdate)
if err != nil || !isFit {
return updated, err
}
@@ -198,7 +205,7 @@ func (c *SyncController) createDefaultResourceQuota(ns *corev1.Namespace, spec *
return err
}
func (c *SyncController) validateAndSetNamespaceQuota(ns *corev1.Namespace) (bool, *corev1.Namespace, error) {
func (c *SyncController) validateAndSetNamespaceQuota(ns *corev1.Namespace, quotaToUpdate string) (bool, *corev1.Namespace, error) {
if ns == nil || ns.DeletionTimestamp != nil {
return true, ns, nil
}
@@ -213,11 +220,6 @@ func (c *SyncController) validateAndSetNamespaceQuota(ns *corev1.Namespace) (boo
return true, ns, err
}
// set default quota if not set
quotaToUpdate, err := c.getResourceQuotaToUpdate(ns)
if err != nil {
return false, ns, err
}
updatedNs := ns.DeepCopy()
if quotaToUpdate != "" {
if updatedNs.Annotations == nil {
@@ -285,22 +287,71 @@ func (c *SyncController) setValidated(ns *corev1.Namespace, value bool, msg stri
}
func (c *SyncController) getResourceQuotaToUpdate(ns *corev1.Namespace) (string, error) {
toCheck := getNamespaceResourceQuota(ns)
quota := getNamespaceResourceQuota(ns)
defaultQuota, err := getProjectNamespaceDefaultQuota(ns, c.ProjectLister)
if err != nil {
return "", err
}
// rework after api framework change is done
// when annotation field is passed as null, the annotation should be removed
// instead of being updated with the null value
if toCheck != "" && toCheck != "null" {
return "", nil
var updatedQuota *v3.NamespaceResourceQuota
if quota != "" && quota != "null" {
// check if fields need to be removed or set
// based on the default quota
var existingQuota v3.NamespaceResourceQuota
err := json.Unmarshal([]byte(convert.ToString(quota)), &existingQuota)
if err != nil {
return "", err
}
updatedQuota, err = completeQuota(&existingQuota, defaultQuota)
if updatedQuota == nil || err != nil {
return "", err
}
}
quota, err := getProjectNamespaceDefaultQuota(ns, c.ProjectLister)
if err != nil {
return "", err
var b []byte
if updatedQuota == nil {
b, err = json.Marshal(defaultQuota)
} else {
b, err = json.Marshal(updatedQuota)
}
b, err := json.Marshal(quota)
if err != nil {
return "", err
}
return string(b), nil
}
func completeQuota(existingQuota *v3.NamespaceResourceQuota, defaultQuota *v3.NamespaceResourceQuota) (*v3.NamespaceResourceQuota, error) {
if defaultQuota == nil {
return nil, nil
}
existingLimitMap, err := convert.EncodeToMap(existingQuota.Limit)
if err != nil {
return nil, err
}
newLimitMap, err := convert.EncodeToMap(defaultQuota.Limit)
if err != nil {
return nil, err
}
for key, value := range existingLimitMap {
if _, ok := newLimitMap[key]; ok {
newLimitMap[key] = value
}
}
if reflect.DeepEqual(existingLimitMap, newLimitMap) {
return nil, nil
}
toReturn := existingQuota.DeepCopy()
newLimit := v3.ResourceQuotaLimit{}
if err := mapstructure.Decode(newLimitMap, &newLimit); err != nil {
return nil, err
}
toReturn.Limit = newLimit
return toReturn, nil
}
Oops, something went wrong.

0 comments on commit da4d0f7

Please sign in to comment.