167 lines
5.9 KiB
Go
167 lines
5.9 KiB
Go
/*
|
|
Copyright 2022 The Karmada Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package federatedresourcequota
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
"k8s.io/klog/v2"
|
|
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
|
|
|
clustervalidation "github.com/karmada-io/karmada/pkg/apis/cluster/validation"
|
|
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
|
"github.com/karmada-io/karmada/pkg/util/lifted"
|
|
)
|
|
|
|
// ValidatingAdmission validates FederatedResourceQuota object when creating/updating.
|
|
type ValidatingAdmission struct {
|
|
Decoder admission.Decoder
|
|
}
|
|
|
|
// Check if our ValidatingAdmission implements necessary interface
|
|
var _ admission.Handler = &ValidatingAdmission{}
|
|
|
|
// Handle implements admission.Handler interface.
|
|
// It yields a response to an AdmissionRequest.
|
|
func (v *ValidatingAdmission) Handle(_ context.Context, req admission.Request) admission.Response {
|
|
quota := &policyv1alpha1.FederatedResourceQuota{}
|
|
|
|
err := v.Decoder.Decode(req, quota)
|
|
if err != nil {
|
|
return admission.Errored(http.StatusBadRequest, err)
|
|
}
|
|
klog.V(2).Infof("Validating FederatedResourceQuote(%s) for request: %s", klog.KObj(quota).String(), req.Operation)
|
|
|
|
if errs := validateFederatedResourceQuota(quota); len(errs) != 0 {
|
|
klog.Errorf("%v", errs)
|
|
return admission.Denied(errs.ToAggregate().Error())
|
|
}
|
|
|
|
return admission.Allowed("")
|
|
}
|
|
|
|
func validateFederatedResourceQuota(quota *policyv1alpha1.FederatedResourceQuota) field.ErrorList {
|
|
errs := field.ErrorList{}
|
|
errs = append(errs, validateFederatedResourceQuotaSpec("a.Spec, field.NewPath("spec"))...)
|
|
errs = append(errs, validateFederatedResourceQuotaStatus("a.Status, field.NewPath("status"))...)
|
|
return errs
|
|
}
|
|
|
|
func validateFederatedResourceQuotaSpec(quotaSpec *policyv1alpha1.FederatedResourceQuotaSpec, fld *field.Path) field.ErrorList {
|
|
errs := field.ErrorList{}
|
|
|
|
errs = append(errs, validateResourceList(quotaSpec.Overall, fld.Child("overall"))...)
|
|
|
|
fldPath := fld.Child("staticAssignments")
|
|
for index := range quotaSpec.StaticAssignments {
|
|
errs = append(errs, validateStaticAssignment("aSpec.StaticAssignments[index], fldPath.Index(index))...)
|
|
}
|
|
|
|
errs = append(errs, validateOverallAndAssignments(quotaSpec, fld)...)
|
|
|
|
return errs
|
|
}
|
|
|
|
func validateStaticAssignment(staticAssignment *policyv1alpha1.StaticClusterAssignment, fld *field.Path) field.ErrorList {
|
|
errs := field.ErrorList{}
|
|
|
|
if errMegs := clustervalidation.ValidateClusterName(staticAssignment.ClusterName); len(errMegs) > 0 {
|
|
errs = append(errs, field.Invalid(fld.Child("clusterName"), staticAssignment.ClusterName, strings.Join(errMegs, ",")))
|
|
}
|
|
errs = append(errs, validateResourceList(staticAssignment.Hard, fld.Child("hard"))...)
|
|
|
|
return errs
|
|
}
|
|
|
|
func validateOverallAndAssignments(quotaSpec *policyv1alpha1.FederatedResourceQuotaSpec, fld *field.Path) field.ErrorList {
|
|
errs := field.ErrorList{}
|
|
|
|
overallPath := fld.Child("overall")
|
|
for k, v := range quotaSpec.Overall {
|
|
assignment := calculateAssignmentForResourceKey(k, quotaSpec.StaticAssignments)
|
|
if v.Cmp(assignment) < 0 {
|
|
errs = append(errs, field.Invalid(overallPath.Key(string(k)), v.String(), "overall is less than assignments"))
|
|
}
|
|
}
|
|
|
|
staticAssignmentsPath := fld.Child("staticAssignments")
|
|
for index, assignment := range quotaSpec.StaticAssignments {
|
|
restPath := staticAssignmentsPath.Index(index).Child("hard")
|
|
for k := range assignment.Hard {
|
|
if _, exist := quotaSpec.Overall[k]; !exist {
|
|
errs = append(errs, field.Invalid(restPath.Key(string(k)), k, "assignment resourceName is not exist in overall"))
|
|
}
|
|
}
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
func calculateAssignmentForResourceKey(resourceKey corev1.ResourceName, staticAssignments []policyv1alpha1.StaticClusterAssignment) resource.Quantity {
|
|
quantity := resource.Quantity{}
|
|
for index := range staticAssignments {
|
|
q, exist := staticAssignments[index].Hard[resourceKey]
|
|
if exist {
|
|
quantity.Add(q)
|
|
}
|
|
}
|
|
return quantity
|
|
}
|
|
|
|
func validateFederatedResourceQuotaStatus(quotaStatus *policyv1alpha1.FederatedResourceQuotaStatus, fld *field.Path) field.ErrorList {
|
|
errs := field.ErrorList{}
|
|
|
|
errs = append(errs, validateResourceList(quotaStatus.Overall, fld.Child("overall"))...)
|
|
errs = append(errs, validateResourceList(quotaStatus.OverallUsed, fld.Child("overallUsed"))...)
|
|
|
|
fldPath := fld.Child("aggregatedStatus")
|
|
for index := range quotaStatus.AggregatedStatus {
|
|
errs = append(errs, validateClusterQuotaStatus("aStatus.AggregatedStatus[index], fldPath.Index(index))...)
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
func validateClusterQuotaStatus(aggregatedStatus *policyv1alpha1.ClusterQuotaStatus, fld *field.Path) field.ErrorList {
|
|
errs := field.ErrorList{}
|
|
|
|
if errMegs := clustervalidation.ValidateClusterName(aggregatedStatus.ClusterName); len(errMegs) > 0 {
|
|
errs = append(errs, field.Invalid(fld.Child("clusterName"), aggregatedStatus.ClusterName, strings.Join(errMegs, ",")))
|
|
}
|
|
errs = append(errs, validateResourceList(aggregatedStatus.Hard, fld.Child("hard"))...)
|
|
errs = append(errs, validateResourceList(aggregatedStatus.Used, fld.Child("used"))...)
|
|
|
|
return errs
|
|
}
|
|
|
|
func validateResourceList(resourceList corev1.ResourceList, fld *field.Path) field.ErrorList {
|
|
errs := field.ErrorList{}
|
|
|
|
for k, v := range resourceList {
|
|
resPath := fld.Key(string(k))
|
|
errs = append(errs, lifted.ValidateResourceQuotaResourceName(string(k), resPath)...)
|
|
errs = append(errs, lifted.ValidateResourceQuantityValue(string(k), v, resPath)...)
|
|
}
|
|
|
|
return errs
|
|
}
|