Merge pull request #1460 from XiShanYongYe-Chang/tidy-up-lifted-file

Tidy up lifted file
This commit is contained in:
karmada-bot 2022-03-15 20:49:31 +08:00 committed by GitHub
commit aef51c0a48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 872 additions and 717 deletions

View File

@ -10,6 +10,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "github.com/karmada-io/karmada/pkg/apis/cluster"
"github.com/karmada-io/karmada/pkg/util/lifted"
)
const clusterNameMaxLength int = 48
@ -86,7 +87,7 @@ func ValidateClusterSpec(spec *api.ClusterSpec, fldPath *field.Path) field.Error
}
if len(spec.Taints) > 0 {
allErrs = append(allErrs, ValidateClusterTaints(spec.Taints, fldPath.Child("taints"))...)
allErrs = append(allErrs, lifted.ValidateClusterTaints(spec.Taints, fldPath.Child("taints"))...)
}
return allErrs

View File

@ -34,6 +34,7 @@ import (
"github.com/karmada-io/karmada/pkg/util/helper"
"github.com/karmada-io/karmada/pkg/util/informermanager"
"github.com/karmada-io/karmada/pkg/util/informermanager/keys"
"github.com/karmada-io/karmada/pkg/util/lifted"
"github.com/karmada-io/karmada/pkg/util/names"
"github.com/karmada-io/karmada/pkg/util/ratelimiter"
"github.com/karmada-io/karmada/pkg/util/restmapper"
@ -152,7 +153,7 @@ var (
func (d *ResourceDetector) discoverResources(period time.Duration) {
wait.Until(func() {
newResources := GetDeletableResources(d.DiscoveryClientSet)
newResources := lifted.GetDeletableResources(d.DiscoveryClientSet)
for r := range newResources {
if d.InformerManager.IsHandlerExist(r, d.EventHandler) || d.gvrDisabled(r) {
continue

View File

@ -15,7 +15,7 @@ import (
"github.com/karmada-io/karmada/pkg/estimator/server/nodes"
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/helper"
utilworkload "github.com/karmada-io/karmada/pkg/util/workload"
utilworkload "github.com/karmada-io/karmada/pkg/util/lifted"
)
// NodeMaxAvailableReplica calculates max available replicas of a node, based on

View File

@ -14,9 +14,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
@ -25,11 +23,9 @@ import (
karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
"github.com/karmada-io/karmada/pkg/generated/clientset/versioned/scheme"
"github.com/karmada-io/karmada/pkg/karmadactl/options"
"github.com/karmada-io/karmada/pkg/util/lifted"
)
// Following parseTaints(),validateTaintEffect(),etc. are directly lifted from the Kubernetes codebase.
// For reference: https://github.com/kubernetes/kubernetes/blob/ed42bbd722a14640f8b5315a521745e7526ff31b/staging/src/k8s.io/kubectl/pkg/cmd/taint/utils.go
// Exported taint constant strings to mark the type of the current operation
const (
MODIFIED = "modified"
@ -115,7 +111,7 @@ func (o *CommandTaintOption) Complete(args []string) error {
return fmt.Errorf("at least one taint update is required")
}
if o.taintsToAdd, o.taintsToRemove, err = parseTaints(taintArgs); err != nil {
if o.taintsToAdd, o.taintsToRemove, err = lifted.ParseTaints(taintArgs); err != nil {
return err
}
@ -276,97 +272,6 @@ func (o *CommandTaintOption) parseTaintArgs(args []string) ([]string, error) {
return taintArgs, nil
}
// parseTaints takes a spec which is an array and creates slices for new taints to be added, taints to be deleted.
// It also validates the spec. For example, the form `<key>` may be used to remove a taint, but not to add one.
func parseTaints(spec []string) ([]corev1.Taint, []corev1.Taint, error) {
var taints, taintsToRemove []corev1.Taint
uniqueTaints := map[corev1.TaintEffect]sets.String{}
for _, taintSpec := range spec {
if strings.HasSuffix(taintSpec, "-") {
taintToRemove, err := parseTaint(strings.TrimSuffix(taintSpec, "-"))
if err != nil {
return nil, nil, err
}
taintsToRemove = append(taintsToRemove, corev1.Taint{Key: taintToRemove.Key, Effect: taintToRemove.Effect})
} else {
newTaint, err := parseTaint(taintSpec)
if err != nil {
return nil, nil, err
}
// validate that the taint has an effect, which is required to add the taint
if len(newTaint.Effect) == 0 {
return nil, nil, fmt.Errorf("invalid taint spec: %v", taintSpec)
}
// validate if taint is unique by <key, effect>
if len(uniqueTaints[newTaint.Effect]) > 0 && uniqueTaints[newTaint.Effect].Has(newTaint.Key) {
return nil, nil, fmt.Errorf("duplicated taints with the same key and effect: %v", newTaint)
}
// add taint to existingTaints for uniqueness check
if len(uniqueTaints[newTaint.Effect]) == 0 {
uniqueTaints[newTaint.Effect] = sets.String{}
}
uniqueTaints[newTaint.Effect].Insert(newTaint.Key)
taints = append(taints, newTaint)
}
}
return taints, taintsToRemove, nil
}
// parseTaint parses a taint from a string, whose form must be either
// '<key>=<value>:<effect>', '<key>:<effect>', or '<key>'.
func parseTaint(st string) (corev1.Taint, error) {
var taint corev1.Taint
var key string
var value string
var effect corev1.TaintEffect
parts := strings.Split(st, ":")
switch len(parts) {
case 1:
key = parts[0]
case 2:
effect = corev1.TaintEffect(parts[1])
if err := validateTaintEffect(effect); err != nil {
return taint, err
}
partsKV := strings.Split(parts[0], "=")
if len(partsKV) > 2 {
return taint, fmt.Errorf("invalid taint spec: %v", st)
}
key = partsKV[0]
if len(partsKV) == 2 {
value = partsKV[1]
if errs := validation.IsValidLabelValue(value); len(errs) > 0 {
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
}
}
default:
return taint, fmt.Errorf("invalid taint spec: %v", st)
}
if errs := validation.IsQualifiedName(key); len(errs) > 0 {
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
}
taint.Key = key
taint.Value = value
taint.Effect = effect
return taint, nil
}
func validateTaintEffect(effect corev1.TaintEffect) error {
if effect != corev1.TaintEffectNoSchedule && effect != corev1.TaintEffectPreferNoSchedule && effect != corev1.TaintEffectNoExecute {
return fmt.Errorf("invalid taint effect: %v, unsupported taint effect", effect)
}
return nil
}
// reorganizeTaints returns the updated set of taints, taking into account old taints that were not updated,
// old taints that were updated, old taints that were deleted, and new taints.
func reorganizeTaints(cluster *clusterv1alpha1.Cluster, overwrite bool, taintsToAdd []corev1.Taint, taintsToRemove []corev1.Taint) (string, []corev1.Taint, error) {

View File

@ -433,166 +433,3 @@ func TestReorganizeTaints(t *testing.T) {
}
}
}
func TestParseTaints(t *testing.T) {
cases := []struct {
name string
spec []string
expectedTaints []corev1.Taint
expectedTaintsToRemove []corev1.Taint
expectedErr bool
}{
{
name: "invalid spec format",
spec: []string{""},
expectedErr: true,
},
{
name: "invalid spec format",
spec: []string{"foo=abc"},
expectedErr: true,
},
{
name: "invalid spec format",
spec: []string{"foo=abc=xyz:NoSchedule"},
expectedErr: true,
},
{
name: "invalid spec format",
spec: []string{"foo=abc:xyz:NoSchedule"},
expectedErr: true,
},
{
name: "invalid spec format for adding taint",
spec: []string{"foo"},
expectedErr: true,
},
{
name: "invalid spec effect for adding taint",
spec: []string{"foo=abc:invalid_effect"},
expectedErr: true,
},
{
name: "invalid spec effect for deleting taint",
spec: []string{"foo:invalid_effect-"},
expectedErr: true,
},
{
name: "add new taints",
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule"},
expectedTaints: []corev1.Taint{
{
Key: "foo",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "baz",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "qux",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "foobar",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
},
expectedErr: false,
},
{
name: "delete taints",
spec: []string{"foo:NoSchedule-", "bar:NoSchedule-", "qux=:NoSchedule-", "dedicated-"},
expectedTaintsToRemove: []corev1.Taint{
{
Key: "foo",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "qux",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "dedicated",
},
},
expectedErr: false,
},
{
name: "add taints and delete taints",
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule", "foo:NoSchedule-", "bar:NoSchedule-", "baz=:NoSchedule-"},
expectedTaints: []corev1.Taint{
{
Key: "foo",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "baz",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "qux",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "foobar",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
},
expectedTaintsToRemove: []corev1.Taint{
{
Key: "foo",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "baz",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
},
expectedErr: false,
},
}
for _, c := range cases {
taints, taintsToRemove, err := parseTaints(c.spec)
if c.expectedErr && err == nil {
t.Errorf("[%s] expected error for spec %s, but got nothing", c.name, c.spec)
}
if !c.expectedErr && err != nil {
t.Errorf("[%s] expected no error for spec %s, but got: %v", c.name, c.spec, err)
}
if !reflect.DeepEqual(c.expectedTaints, taints) {
t.Errorf("[%s] expected returen taints as %v, but got: %v", c.name, c.expectedTaints, taints)
}
if !reflect.DeepEqual(c.expectedTaintsToRemove, taintsToRemove) {
t.Errorf("[%s] expected return taints to be removed as %v, but got: %v", c.name, c.expectedTaintsToRemove, taintsToRemove)
}
}
}

View File

@ -8,10 +8,12 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/helper"
"github.com/karmada-io/karmada/pkg/util/lifted"
)
type dependenciesInterpreter func(object *unstructured.Unstructured) ([]configv1alpha1.DependentObjectReference, error)
@ -32,7 +34,7 @@ func getDeploymentDependencies(object *unstructured.Unstructured) ([]configv1alp
return nil, fmt.Errorf("failed to convert Deployment from unstructured object: %v", err)
}
podObj, err := GetPodFromTemplate(&deploymentObj.Spec.Template, deploymentObj, nil)
podObj, err := lifted.GetPodFromTemplate(&deploymentObj.Spec.Template, deploymentObj, nil)
if err != nil {
return nil, err
}
@ -46,7 +48,7 @@ func getJobDependencies(object *unstructured.Unstructured) ([]configv1alpha1.Dep
return nil, fmt.Errorf("failed to convert Job from unstructured object: %v", err)
}
podObj, err := GetPodFromTemplate(&jobObj.Spec.Template, jobObj, nil)
podObj, err := lifted.GetPodFromTemplate(&jobObj.Spec.Template, jobObj, nil)
if err != nil {
return nil, err
}
@ -69,7 +71,7 @@ func getDaemonSetDependencies(object *unstructured.Unstructured) ([]configv1alph
return nil, fmt.Errorf("failed to convert DaemonSet from unstructured object: %v", err)
}
podObj, err := GetPodFromTemplate(&daemonSetObj.Spec.Template, daemonSetObj, nil)
podObj, err := lifted.GetPodFromTemplate(&daemonSetObj.Spec.Template, daemonSetObj, nil)
if err != nil {
return nil, err
}
@ -83,7 +85,7 @@ func getStatefulSetDependencies(object *unstructured.Unstructured) ([]configv1al
return nil, fmt.Errorf("failed to convert StatefulSet from unstructured object: %v", err)
}
podObj, err := GetPodFromTemplate(&statefulSetObj.Spec.Template, statefulSetObj, nil)
podObj, err := lifted.GetPodFromTemplate(&statefulSetObj.Spec.Template, statefulSetObj, nil)
if err != nil {
return nil, err
}
@ -116,3 +118,21 @@ func getDependenciesFromPodTemplate(podObj *corev1.Pod) ([]configv1alpha1.Depend
return dependentObjectRefs, nil
}
func getSecretNames(pod *corev1.Pod) sets.String {
result := sets.NewString()
lifted.VisitPodSecretNames(pod, func(name string) bool {
result.Insert(name)
return true
})
return result
}
func getConfigMapNames(pod *corev1.Pod) sets.String {
result := sets.NewString()
lifted.VisitPodConfigmapNames(pod, func(name string) bool {
result.Insert(name)
return true
})
return result
}

View File

@ -10,6 +10,7 @@ import (
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/helper"
"github.com/karmada-io/karmada/pkg/util/lifted"
)
// retentionInterpreter is the function that retains values from "observed" object.
@ -18,24 +19,13 @@ type retentionInterpreter func(desired *unstructured.Unstructured, observed *uns
func getAllDefaultRetentionInterpreter() map[schema.GroupVersionKind]retentionInterpreter {
s := make(map[schema.GroupVersionKind]retentionInterpreter)
s[corev1.SchemeGroupVersion.WithKind(util.PodKind)] = retainPodFields
s[corev1.SchemeGroupVersion.WithKind(util.ServiceKind)] = retainServiceFields
s[corev1.SchemeGroupVersion.WithKind(util.ServiceAccountKind)] = retainServiceAccountFields
s[corev1.SchemeGroupVersion.WithKind(util.ServiceKind)] = lifted.RetainServiceFields
s[corev1.SchemeGroupVersion.WithKind(util.ServiceAccountKind)] = lifted.RetainServiceAccountFields
s[corev1.SchemeGroupVersion.WithKind(util.PersistentVolumeClaimKind)] = retainPersistentVolumeClaimFields
s[batchv1.SchemeGroupVersion.WithKind(util.JobKind)] = retainJobSelectorFields
return s
}
/*
This code is directly lifted from the kubefed codebase. It's a list of functions to update the desired object with values retained
from the cluster object.
For reference: https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/sync/dispatch/retain.go#L27-L133
*/
const (
// SecretsField indicates the 'secrets' field of a service account
SecretsField = "secrets"
)
func retainPodFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
desiredPod, err := helper.ConvertToPod(desired)
if err != nil {
@ -68,80 +58,6 @@ func retainPodFields(desired, observed *unstructured.Unstructured) (*unstructure
return unstructuredObj, nil
}
// retainServiceFields updates the desired service object with values retained from the cluster object.
func retainServiceFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
// healthCheckNodePort is allocated by APIServer and unchangeable, so it should be retained while updating
if err := retainServiceHealthCheckNodePort(desired, observed); err != nil {
return nil, err
}
// ClusterIP is allocated to Service by cluster, so retain the same if any while updating
if err := retainServiceClusterIP(desired, observed); err != nil {
return nil, err
}
return desired, nil
}
func retainServiceHealthCheckNodePort(desired, observed *unstructured.Unstructured) error {
healthCheckNodePort, ok, err := unstructured.NestedInt64(observed.Object, "spec", "healthCheckNodePort")
if err != nil {
return fmt.Errorf("error retrieving healthCheckNodePort from service: %w", err)
}
if ok && healthCheckNodePort > 0 {
if err = unstructured.SetNestedField(desired.Object, healthCheckNodePort, "spec", "healthCheckNodePort"); err != nil {
return fmt.Errorf("error setting healthCheckNodePort for service: %w", err)
}
}
return nil
}
func retainServiceClusterIP(desired, observed *unstructured.Unstructured) error {
clusterIP, ok, err := unstructured.NestedString(observed.Object, "spec", "clusterIP")
if err != nil {
return fmt.Errorf("error retrieving clusterIP from cluster service: %w", err)
}
// !ok could indicate that a cluster ip was not assigned
if ok && clusterIP != "" {
err = unstructured.SetNestedField(desired.Object, clusterIP, "spec", "clusterIP")
if err != nil {
return fmt.Errorf("error setting clusterIP for service: %w", err)
}
}
return nil
}
// retainServiceAccountFields retains the 'secrets' field of a service account
// if the desired representation does not include a value for the field. This
// ensures that the sync controller doesn't continually clear a generated
// secret from a service account, prompting continual regeneration by the
// service account controller in the member cluster.
func retainServiceAccountFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
// Check whether the secrets field is populated in the desired object.
desiredSecrets, ok, err := unstructured.NestedSlice(desired.Object, SecretsField)
if err != nil {
return nil, fmt.Errorf("error retrieving secrets from desired service account: %w", err)
}
if ok && len(desiredSecrets) > 0 {
// Field is populated, so an update to the target resource does not
// risk triggering a race with the service account controller.
return desired, nil
}
// Retrieve the secrets from the cluster object and retain them.
secrets, ok, err := unstructured.NestedSlice(observed.Object, SecretsField)
if err != nil {
return nil, fmt.Errorf("error retrieving secrets from service account: %w", err)
}
if ok && len(secrets) > 0 {
err := unstructured.SetNestedField(desired.Object, secrets, SecretsField)
if err != nil {
return nil, fmt.Errorf("error setting secrets for service account: %w", err)
}
}
return desired, nil
}
func retainPersistentVolumeClaimFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
// volumeName is allocated by member cluster and unchangeable, so it should be retained while updating
volumeName, ok, err := unstructured.NestedString(observed.Object, "spec", "volumeName")

View File

@ -18,7 +18,7 @@ limitations under the License.
// However the code has been revised for using Lister instead of API interface.
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/controller/deployment/util/deployment_util.go
package workload
package lifted
import (
"sort"

View File

@ -1,4 +1,4 @@
package detector
package lifted
import (
"k8s.io/apimachinery/pkg/runtime/schema"

View File

@ -0,0 +1,63 @@
/*
Copyright 2020 The Kubernetes 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.
*/
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
// For reference:
// https://github.com/kubernetes/kubernetes/blob/release-1.20/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go#L193-L225
package lifted
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
)
// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements
// labels.Selector.
func NodeSelectorRequirementsAsSelector(nsm []corev1.NodeSelectorRequirement) (labels.Selector, error) {
if len(nsm) == 0 {
return labels.Nothing(), nil
}
selector := labels.NewSelector()
for _, expr := range nsm {
var op selection.Operator
switch expr.Operator {
case corev1.NodeSelectorOpIn:
op = selection.In
case corev1.NodeSelectorOpNotIn:
op = selection.NotIn
case corev1.NodeSelectorOpExists:
op = selection.Exists
case corev1.NodeSelectorOpDoesNotExist:
op = selection.DoesNotExist
case corev1.NodeSelectorOpGt:
op = selection.GreaterThan
case corev1.NodeSelectorOpLt:
op = selection.LessThan
default:
return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator)
}
r, err := labels.NewRequirement(expr.Key, op, expr.Values)
if err != nil {
return nil, err
}
selector = selector.Add(*r)
}
return selector, nil
}

View File

@ -0,0 +1,79 @@
/*
Copyright 2016 The Kubernetes 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.
*/
// This code is lifted from the kubefed codebase. It's a list of functions to determine whether the provided cluster
// object needs to be updated according to the desired object and the recorded version.
// For reference: https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/util/propagatedversion.go#L30-L59
package lifted
import (
"fmt"
"reflect"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const (
generationPrefix = "gen:"
resourceVersionPrefix = "rv:"
)
// ObjectVersion retrieves the field type-prefixed value used for
// determining currency of the given cluster object.
func ObjectVersion(clusterObj *unstructured.Unstructured) string {
generation := clusterObj.GetGeneration()
if generation != 0 {
return fmt.Sprintf("%s%d", generationPrefix, generation)
}
return fmt.Sprintf("%s%s", resourceVersionPrefix, clusterObj.GetResourceVersion())
}
// ObjectNeedsUpdate determines whether the 2 objects provided cluster
// object needs to be updated according to the desired object and the
// recorded version.
func ObjectNeedsUpdate(desiredObj, clusterObj *unstructured.Unstructured, recordedVersion string) bool {
targetVersion := ObjectVersion(clusterObj)
if recordedVersion != targetVersion {
return true
}
// If versions match and the version is sourced from the
// generation field, a further check of metadata equivalency is
// required.
return strings.HasPrefix(targetVersion, generationPrefix) && !objectMetaObjEquivalent(desiredObj, clusterObj)
}
// objectMetaObjEquivalent checks if cluster-independent, user provided data in two given ObjectMeta are equal. If in
// the future the ObjectMeta structure is expanded then any field that is not populated
// by the api server should be included here.
func objectMetaObjEquivalent(a, b metav1.Object) bool {
if a.GetName() != b.GetName() {
return false
}
if a.GetNamespace() != b.GetNamespace() {
return false
}
aLabels := a.GetLabels()
bLabels := b.GetLabels()
if !reflect.DeepEqual(aLabels, bLabels) && (len(aLabels) != 0 || len(bLabels) != 0) {
return false
}
return true
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2014 The Kubernetes 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.
*/
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
// For reference:
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/controller/controller_utils.go#L466-L495
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/controller/controller_utils.go#L539-L562
package lifted
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
func getPodsLabelSet(template *corev1.PodTemplateSpec) labels.Set {
desiredLabels := make(labels.Set)
for k, v := range template.Labels {
desiredLabels[k] = v
}
return desiredLabels
}
func getPodsFinalizers(template *corev1.PodTemplateSpec) []string {
desiredFinalizers := make([]string, len(template.Finalizers))
copy(desiredFinalizers, template.Finalizers)
return desiredFinalizers
}
func getPodsAnnotationSet(template *corev1.PodTemplateSpec) labels.Set {
desiredAnnotations := make(labels.Set)
for k, v := range template.Annotations {
desiredAnnotations[k] = v
}
return desiredAnnotations
}
func getPodsPrefix(controllerName string) string {
// use the dash (if the name isn't too long) to make the pod name a bit prettier
prefix := fmt.Sprintf("%s-", controllerName)
if len(ValidatePodName(prefix, true)) != 0 {
prefix = controllerName
}
return prefix
}
// GetPodFromTemplate generates pod object from a template.
func GetPodFromTemplate(template *corev1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*corev1.Pod, error) {
desiredLabels := getPodsLabelSet(template)
desiredFinalizers := getPodsFinalizers(template)
desiredAnnotations := getPodsAnnotationSet(template)
accessor, err := meta.Accessor(parentObject)
if err != nil {
return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err)
}
prefix := getPodsPrefix(accessor.GetName())
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: desiredLabels,
Annotations: desiredAnnotations,
GenerateName: prefix,
Finalizers: desiredFinalizers,
Namespace: accessor.GetNamespace(),
},
}
if controllerRef != nil {
pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef)
}
pod.Spec = *template.Spec.DeepCopy()
return pod, nil
}

View File

@ -16,11 +16,9 @@ limitations under the License.
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
// For reference:
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/helper/helpers.go#L57-L61
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/helper/helpers.go#L169-L192
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/helper/helpers.go#L212-L283
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/v1/helper/helpers.go
package helper
package lifted
import (
"fmt"
@ -31,12 +29,6 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
)
// IsQuotaHugePageResourceName returns true if the resource name has the quota
// related huge page resource prefix.
func IsQuotaHugePageResourceName(name corev1.ResourceName) bool {
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix) || strings.HasPrefix(string(name), corev1.ResourceRequestsHugePagesPrefix)
}
// IsExtendedResourceName returns true if:
// 1. the resource name is not in the default namespace;
// 2. resource name does not have "requests." prefix,
@ -54,12 +46,35 @@ func IsExtendedResourceName(name corev1.ResourceName) bool {
return true
}
// IsPrefixedNativeResource returns true if the resource name is in the
// *kubernetes.io/ namespace.
func IsPrefixedNativeResource(name corev1.ResourceName) bool {
return strings.Contains(string(name), corev1.ResourceDefaultNamespacePrefix)
}
// IsNativeResource returns true if the resource name is in the
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
// implicitly in the kubernetes.io/ namespace.
func IsNativeResource(name corev1.ResourceName) bool {
return !strings.Contains(string(name), "/") ||
strings.Contains(string(name), corev1.ResourceDefaultNamespacePrefix)
IsPrefixedNativeResource(name)
}
// IsHugePageResourceName returns true if the resource name has the huge page
// resource prefix.
func IsHugePageResourceName(name corev1.ResourceName) bool {
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
}
// IsAttachableVolumeResourceName returns true when the resource name is prefixed in attachable volume
func IsAttachableVolumeResourceName(name corev1.ResourceName) bool {
return strings.HasPrefix(string(name), corev1.ResourceAttachableVolumesPrefix)
}
// IsQuotaHugePageResourceName returns true if the resource name has the quota
// related huge page resource prefix.
func IsQuotaHugePageResourceName(name corev1.ResourceName) bool {
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix) || strings.HasPrefix(string(name), corev1.ResourceRequestsHugePagesPrefix)
}
var standardQuotaResources = sets.NewString(

View File

@ -0,0 +1,31 @@
/*
Copyright 2017 The Kubernetes 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.
*/
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
// For reference:
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/scheduler/util/utils.go#L144-L148
package lifted
import (
corev1 "k8s.io/api/core/v1"
)
// IsScalarResourceName validates the resource for Extended, Hugepages, Native and AttachableVolume resources
func IsScalarResourceName(name corev1.ResourceName) bool {
return IsExtendedResourceName(name) || IsHugePageResourceName(name) ||
IsPrefixedNativeResource(name) || IsAttachableVolumeResourceName(name)
}

106
pkg/util/lifted/retain.go Normal file
View File

@ -0,0 +1,106 @@
/*
Copyright 2020 The Kubernetes 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.
*/
// This code is directly lifted from the kubefed codebase.
// For reference:
// https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/sync/dispatch/retain.go#L48-L155
package lifted
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const (
// SecretsField indicates the 'secrets' field of a service account
SecretsField = "secrets"
)
// RetainServiceFields updates the desired service object with values retained from the cluster object.
func RetainServiceFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
// healthCheckNodePort is allocated by APIServer and unchangeable, so it should be retained while updating
if err := retainServiceHealthCheckNodePort(desired, observed); err != nil {
return nil, err
}
// ClusterIP is allocated to Service by cluster, so retain the same if any while updating
if err := retainServiceClusterIP(desired, observed); err != nil {
return nil, err
}
return desired, nil
}
func retainServiceHealthCheckNodePort(desired, observed *unstructured.Unstructured) error {
healthCheckNodePort, ok, err := unstructured.NestedInt64(observed.Object, "spec", "healthCheckNodePort")
if err != nil {
return fmt.Errorf("error retrieving healthCheckNodePort from service: %w", err)
}
if ok && healthCheckNodePort > 0 {
if err = unstructured.SetNestedField(desired.Object, healthCheckNodePort, "spec", "healthCheckNodePort"); err != nil {
return fmt.Errorf("error setting healthCheckNodePort for service: %w", err)
}
}
return nil
}
func retainServiceClusterIP(desired, observed *unstructured.Unstructured) error {
clusterIP, ok, err := unstructured.NestedString(observed.Object, "spec", "clusterIP")
if err != nil {
return fmt.Errorf("error retrieving clusterIP from cluster service: %w", err)
}
// !ok could indicate that a cluster ip was not assigned
if ok && clusterIP != "" {
err = unstructured.SetNestedField(desired.Object, clusterIP, "spec", "clusterIP")
if err != nil {
return fmt.Errorf("error setting clusterIP for service: %w", err)
}
}
return nil
}
// RetainServiceAccountFields retains the 'secrets' field of a service account
// if the desired representation does not include a value for the field. This
// ensures that the sync controller doesn't continually clear a generated
// secret from a service account, prompting continual regeneration by the
// service account controller in the member cluster.
func RetainServiceAccountFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
// Check whether the secrets field is populated in the desired object.
desiredSecrets, ok, err := unstructured.NestedSlice(desired.Object, SecretsField)
if err != nil {
return nil, fmt.Errorf("error retrieving secrets from desired service account: %w", err)
}
if ok && len(desiredSecrets) > 0 {
// Field is populated, so an update to the target resource does not
// risk triggering a race with the service account controller.
return desired, nil
}
// Retrieve the secrets from the cluster object and retain them.
secrets, ok, err := unstructured.NestedSlice(observed.Object, SecretsField)
if err != nil {
return nil, fmt.Errorf("error retrieving secrets from service account: %w", err)
}
if ok && len(secrets) > 0 {
err := unstructured.SetNestedField(desired.Object, secrets, SecretsField)
if err != nil {
return nil, fmt.Errorf("error setting secrets for service account: %w", err)
}
}
return desired, nil
}

121
pkg/util/lifted/taint.go Normal file
View File

@ -0,0 +1,121 @@
/*
Copyright 2018 The Kubernetes 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.
*/
// This code is directly lifted from the Kubernetes codebase.
// For reference:
// https://github.com/kubernetes/kubernetes/blob/ed42bbd722a14640f8b5315a521745e7526ff31b/staging/src/k8s.io/kubectl/pkg/cmd/taint/utils.go#L37-L126
package lifted
import (
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
)
// ParseTaints takes a spec which is an array and creates slices for new taints to be added, taints to be deleted.
// It also validates the spec. For example, the form `<key>` may be used to remove a taint, but not to add one.
func ParseTaints(spec []string) ([]corev1.Taint, []corev1.Taint, error) {
var taints, taintsToRemove []corev1.Taint
uniqueTaints := map[corev1.TaintEffect]sets.String{}
for _, taintSpec := range spec {
if strings.HasSuffix(taintSpec, "-") {
taintToRemove, err := parseTaint(strings.TrimSuffix(taintSpec, "-"))
if err != nil {
return nil, nil, err
}
taintsToRemove = append(taintsToRemove, corev1.Taint{Key: taintToRemove.Key, Effect: taintToRemove.Effect})
} else {
newTaint, err := parseTaint(taintSpec)
if err != nil {
return nil, nil, err
}
// validate that the taint has an effect, which is required to add the taint
if len(newTaint.Effect) == 0 {
return nil, nil, fmt.Errorf("invalid taint spec: %v", taintSpec)
}
// validate if taint is unique by <key, effect>
if len(uniqueTaints[newTaint.Effect]) > 0 && uniqueTaints[newTaint.Effect].Has(newTaint.Key) {
return nil, nil, fmt.Errorf("duplicated taints with the same key and effect: %v", newTaint)
}
// add taint to existingTaints for uniqueness check
if len(uniqueTaints[newTaint.Effect]) == 0 {
uniqueTaints[newTaint.Effect] = sets.String{}
}
uniqueTaints[newTaint.Effect].Insert(newTaint.Key)
taints = append(taints, newTaint)
}
}
return taints, taintsToRemove, nil
}
// parseTaint parses a taint from a string, whose form must be either
// '<key>=<value>:<effect>', '<key>:<effect>', or '<key>'.
func parseTaint(st string) (corev1.Taint, error) {
var taint corev1.Taint
var key string
var value string
var effect corev1.TaintEffect
parts := strings.Split(st, ":")
switch len(parts) {
case 1:
key = parts[0]
case 2:
effect = corev1.TaintEffect(parts[1])
if err := validateTaintEffect(effect); err != nil {
return taint, err
}
partsKV := strings.Split(parts[0], "=")
if len(partsKV) > 2 {
return taint, fmt.Errorf("invalid taint spec: %v", st)
}
key = partsKV[0]
if len(partsKV) == 2 {
value = partsKV[1]
if errs := validation.IsValidLabelValue(value); len(errs) > 0 {
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
}
}
default:
return taint, fmt.Errorf("invalid taint spec: %v", st)
}
if errs := validation.IsQualifiedName(key); len(errs) > 0 {
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
}
taint.Key = key
taint.Value = value
taint.Effect = effect
return taint, nil
}
func validateTaintEffect(effect corev1.TaintEffect) error {
if effect != corev1.TaintEffectNoSchedule && effect != corev1.TaintEffectPreferNoSchedule && effect != corev1.TaintEffectNoExecute {
return fmt.Errorf("invalid taint effect: %v, unsupported taint effect", effect)
}
return nil
}

View File

@ -0,0 +1,191 @@
/*
Copyright 2016 The Kubernetes 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.
*/
// This code is directly lifted from the Kubernetes codebase.
// For reference:
// https://github.com/kubernetes/kubernetes/blob/ed42bbd722a14640f8b5315a521745e7526ff31b/staging/src/k8s.io/kubectl/pkg/cmd/taint/utils_test.go#L372-L533
package lifted
import (
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
)
func TestParseTaints(t *testing.T) {
cases := []struct {
name string
spec []string
expectedTaints []corev1.Taint
expectedTaintsToRemove []corev1.Taint
expectedErr bool
}{
{
name: "invalid spec format",
spec: []string{""},
expectedErr: true,
},
{
name: "invalid spec format",
spec: []string{"foo=abc"},
expectedErr: true,
},
{
name: "invalid spec format",
spec: []string{"foo=abc=xyz:NoSchedule"},
expectedErr: true,
},
{
name: "invalid spec format",
spec: []string{"foo=abc:xyz:NoSchedule"},
expectedErr: true,
},
{
name: "invalid spec format for adding taint",
spec: []string{"foo"},
expectedErr: true,
},
{
name: "invalid spec effect for adding taint",
spec: []string{"foo=abc:invalid_effect"},
expectedErr: true,
},
{
name: "invalid spec effect for deleting taint",
spec: []string{"foo:invalid_effect-"},
expectedErr: true,
},
{
name: "add new taints",
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule"},
expectedTaints: []corev1.Taint{
{
Key: "foo",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "baz",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "qux",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "foobar",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
},
expectedErr: false,
},
{
name: "delete taints",
spec: []string{"foo:NoSchedule-", "bar:NoSchedule-", "qux=:NoSchedule-", "dedicated-"},
expectedTaintsToRemove: []corev1.Taint{
{
Key: "foo",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "qux",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "dedicated",
},
},
expectedErr: false,
},
{
name: "add taints and delete taints",
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule", "foo:NoSchedule-", "bar:NoSchedule-", "baz=:NoSchedule-"},
expectedTaints: []corev1.Taint{
{
Key: "foo",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Value: "abc",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "baz",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "qux",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "foobar",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
},
expectedTaintsToRemove: []corev1.Taint{
{
Key: "foo",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "bar",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "baz",
Value: "",
Effect: corev1.TaintEffectNoSchedule,
},
},
expectedErr: false,
},
}
for _, c := range cases {
taints, taintsToRemove, err := ParseTaints(c.spec)
if c.expectedErr && err == nil {
t.Errorf("[%s] expected error for spec %s, but got nothing", c.name, c.spec)
}
if !c.expectedErr && err != nil {
t.Errorf("[%s] expected no error for spec %s, but got: %v", c.name, c.spec, err)
}
if !reflect.DeepEqual(c.expectedTaints, taints) {
t.Errorf("[%s] expected returen taints as %v, but got: %v", c.name, c.expectedTaints, taints)
}
if !reflect.DeepEqual(c.expectedTaintsToRemove, taintsToRemove) {
t.Errorf("[%s] expected return taints to be removed as %v, but got: %v", c.name, c.expectedTaintsToRemove, taintsToRemove)
}
}
}

View File

@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
package lifted
import (
"strings"
@ -40,7 +40,7 @@ func ValidateClusterTaints(taints []corev1.Taint, fldPath *field.Path) field.Err
allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";")))
}
// validate the taint effect
allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...)
allErrors = append(allErrors, validateClusterTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...)
// validate if taint is unique by <key, effect>
if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) {
@ -59,7 +59,7 @@ func ValidateClusterTaints(taints []corev1.Taint, fldPath *field.Path) field.Err
return allErrors
}
func validateTaintEffect(effect *corev1.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList {
func validateClusterTaintEffect(effect *corev1.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList {
if !allowEmpty && len(*effect) == 0 {
return field.ErrorList{field.Required(fldPath, "")}
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2014 The Kubernetes 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.
*/
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
// For reference:
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L225-L228
package lifted
import apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
// ValidatePodName can be used to check whether the given pod name is valid.
// Prefix indicates this name will be used as part of generation, in which case
// trailing dashes are allowed.
var ValidatePodName = apimachineryvalidation.NameIsDNSSubdomain

View File

@ -23,7 +23,7 @@ limitations under the License.
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L5073-L5084
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L5651-L5661
package federatedresourcequota
package lifted
import (
"strings"
@ -32,8 +32,6 @@ import (
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"github.com/karmada-io/karmada/pkg/webhook/federatedresourcequota/helper"
)
const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg
@ -46,7 +44,7 @@ func ValidateResourceQuotaResourceName(value string, fldPath *field.Path) field.
allErrs := validateResourceName(value, fldPath)
if len(strings.Split(value, "/")) == 1 {
if !helper.IsStandardQuotaResourceName(value) {
if !IsStandardQuotaResourceName(value) {
return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource))
}
}
@ -65,7 +63,7 @@ func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
}
if len(strings.Split(value, "/")) == 1 {
if !helper.IsStandardResourceName(value) {
if !IsStandardResourceName(value) {
return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified"))
}
}
@ -77,7 +75,7 @@ func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
if helper.IsIntegerResourceName(resource) {
if IsIntegerResourceName(resource) {
if value.MilliValue()%int64(1000) != int64(0) {
allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2014 The Kubernetes Authors.
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,31 +15,15 @@ limitations under the License.
*/
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/api/v1/pod/util.go,
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L228,
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/kubelet/configmap/configmap_manager.go#L101-L108,
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/kubelet/secret/secret_manager.go#L102-L109
//
// For reference:
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/api/v1/pod/util.go
package defaultinterpreter
package lifted
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
)
// ValidatePodName can be used to check whether the given pod name is valid.
// Prefix indicates this name will be used as part of generation, in which case
// trailing dashes are allowed.
var ValidatePodName = apimachineryvalidation.NameIsDNSSubdomain
// ContainerType signifies container type
type ContainerType int
@ -55,13 +39,24 @@ const (
// AllContainers specifies that all containers be visited
const AllContainers ContainerType = (InitContainers | Containers | EphemeralContainers)
// Visitor is called with each object name, and returns true if visiting should continue
type Visitor func(name string) (shouldContinue bool)
// ContainerVisitor is called with each container spec, and returns true
// if visiting should continue.
type ContainerVisitor func(container *corev1.Container, containerType ContainerType) (shouldContinue bool)
// Visitor is called with each object name, and returns true if visiting should continue
type Visitor func(name string) (shouldContinue bool)
func skipEmptyNames(visitor Visitor) Visitor {
return func(name string) bool {
if len(name) == 0 {
// continue visiting
return true
}
// delegate to visitor
return visitor(name)
}
}
// VisitContainers invokes the visitor function with a pointer to every container
// spec in the given pod spec with type set in mask. If visitor returns false,
// visiting is short-circuited. VisitContainers returns true if visiting completes,
@ -91,141 +86,6 @@ func VisitContainers(podSpec *corev1.PodSpec, mask ContainerType, visitor Contai
return true
}
func visitContainerSecretNames(container *corev1.Container, visitor Visitor) bool {
for _, env := range container.EnvFrom {
if env.SecretRef != nil {
if !visitor(env.SecretRef.Name) {
return false
}
}
}
for _, envVar := range container.Env {
if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
if !visitor(envVar.ValueFrom.SecretKeyRef.Name) {
return false
}
}
}
return true
}
func visitContainerConfigmapNames(container *corev1.Container, visitor Visitor) bool {
for _, env := range container.EnvFrom {
if env.ConfigMapRef != nil {
if !visitor(env.ConfigMapRef.Name) {
return false
}
}
}
for _, envVar := range container.Env {
if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil {
if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) {
return false
}
}
}
return true
}
// GetPodFromTemplate generates pod object from a template.
func GetPodFromTemplate(template *corev1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*corev1.Pod, error) {
desiredLabels := getPodsLabelSet(template)
desiredFinalizers := getPodsFinalizers(template)
desiredAnnotations := getPodsAnnotationSet(template)
accessor, err := meta.Accessor(parentObject)
if err != nil {
return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err)
}
prefix := getPodsPrefix(accessor.GetName())
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: desiredLabels,
Annotations: desiredAnnotations,
GenerateName: prefix,
Finalizers: desiredFinalizers,
Namespace: accessor.GetNamespace(),
},
}
if controllerRef != nil {
pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef)
}
pod.Spec = *template.Spec.DeepCopy()
return pod, nil
}
func getPodsLabelSet(template *corev1.PodTemplateSpec) labels.Set {
desiredLabels := make(labels.Set)
for k, v := range template.Labels {
desiredLabels[k] = v
}
return desiredLabels
}
func getPodsFinalizers(template *corev1.PodTemplateSpec) []string {
desiredFinalizers := make([]string, len(template.Finalizers))
copy(desiredFinalizers, template.Finalizers)
return desiredFinalizers
}
func getPodsAnnotationSet(template *corev1.PodTemplateSpec) labels.Set {
desiredAnnotations := make(labels.Set)
for k, v := range template.Annotations {
desiredAnnotations[k] = v
}
return desiredAnnotations
}
func getPodsPrefix(controllerName string) string {
// use the dash (if the name isn't too long) to make the pod name a bit prettier
prefix := fmt.Sprintf("%s-", controllerName)
if len(ValidatePodName(prefix, true)) != 0 {
prefix = controllerName
}
return prefix
}
func skipEmptyNames(visitor Visitor) Visitor {
return func(name string) bool {
if len(name) == 0 {
// continue visiting
return true
}
// delegate to visitor
return visitor(name)
}
}
// VisitPodConfigmapNames invokes the visitor function with the name of every configmap
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
// Returns true if visiting completed, false if visiting was short-circuited.
func VisitPodConfigmapNames(pod *corev1.Pod, visitor Visitor) bool {
visitor = skipEmptyNames(visitor)
VisitContainers(&pod.Spec, AllContainers, func(c *corev1.Container, containerType ContainerType) bool {
return visitContainerConfigmapNames(c, visitor)
})
var source *corev1.VolumeSource
for i := range pod.Spec.Volumes {
source = &pod.Spec.Volumes[i].VolumeSource
switch {
case source.Projected != nil:
for j := range source.Projected.Sources {
if source.Projected.Sources[j].ConfigMap != nil {
if !visitor(source.Projected.Sources[j].ConfigMap.Name) {
return false
}
}
}
case source.ConfigMap != nil:
if !visitor(source.ConfigMap.Name) {
return false
}
}
}
return true
}
// VisitPodSecretNames invokes the visitor function with the name of every secret
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
@ -299,20 +159,68 @@ func VisitPodSecretNames(pod *corev1.Pod, visitor Visitor) bool {
return true
}
func getSecretNames(pod *corev1.Pod) sets.String {
result := sets.NewString()
VisitPodSecretNames(pod, func(name string) bool {
result.Insert(name)
func visitContainerSecretNames(container *corev1.Container, visitor Visitor) bool {
for _, env := range container.EnvFrom {
if env.SecretRef != nil {
if !visitor(env.SecretRef.Name) {
return false
}
}
}
for _, envVar := range container.Env {
if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
if !visitor(envVar.ValueFrom.SecretKeyRef.Name) {
return false
}
}
}
return true
})
return result
}
func getConfigMapNames(pod *corev1.Pod) sets.String {
result := sets.NewString()
VisitPodConfigmapNames(pod, func(name string) bool {
result.Insert(name)
return true
// VisitPodConfigmapNames invokes the visitor function with the name of every configmap
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
// Returns true if visiting completed, false if visiting was short-circuited.
func VisitPodConfigmapNames(pod *corev1.Pod, visitor Visitor) bool {
visitor = skipEmptyNames(visitor)
VisitContainers(&pod.Spec, AllContainers, func(c *corev1.Container, containerType ContainerType) bool {
return visitContainerConfigmapNames(c, visitor)
})
return result
var source *corev1.VolumeSource
for i := range pod.Spec.Volumes {
source = &pod.Spec.Volumes[i].VolumeSource
switch {
case source.Projected != nil:
for j := range source.Projected.Sources {
if source.Projected.Sources[j].ConfigMap != nil {
if !visitor(source.Projected.Sources[j].ConfigMap.Name) {
return false
}
}
}
case source.ConfigMap != nil:
if !visitor(source.ConfigMap.Name) {
return false
}
}
}
return true
}
func visitContainerConfigmapNames(container *corev1.Container, visitor Visitor) bool {
for _, env := range container.EnvFrom {
if env.ConfigMapRef != nil {
if !visitor(env.ConfigMapRef.Name) {
return false
}
}
}
for _, envVar := range container.Env {
if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil {
if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) {
return false
}
}
}
return true
}

View File

@ -3,8 +3,6 @@ package objectwatcher
import (
"context"
"fmt"
"reflect"
"strings"
"sync"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -19,14 +17,10 @@ import (
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/resourceinterpreter"
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/lifted"
"github.com/karmada-io/karmada/pkg/util/restmapper"
)
const (
generationPrefix = "gen:"
resourceVersionPrefix = "rv:"
)
// ObjectWatcher manages operations for object dispatched to member clusters.
type ObjectWatcher interface {
Create(clusterName string, desireObj *unstructured.Unstructured) error
@ -213,7 +207,7 @@ func (o *objectWatcherImpl) genObjectKey(obj *unstructured.Unstructured) string
// recordVersion will add or update resource version records
func (o *objectWatcherImpl) recordVersion(clusterObj *unstructured.Unstructured, clusterName string) {
objVersion := objectVersion(clusterObj)
objVersion := lifted.ObjectVersion(clusterObj)
objectKey := o.genObjectKey(clusterObj)
if o.isClusterVersionRecordExist(clusterName) {
o.updateVersionRecord(clusterName, objectKey, objVersion)
@ -270,55 +264,5 @@ func (o *objectWatcherImpl) NeedsUpdate(clusterName string, desiredObj, clusterO
return false, fmt.Errorf("failed to update resource(kind=%s, %s/%s) in cluster %s for the version record does not exist", desiredObj.GetKind(), desiredObj.GetNamespace(), desiredObj.GetName(), clusterName)
}
return objectNeedsUpdate(desiredObj, clusterObj, version), nil
}
/*
This code is lifted from the kubefed codebase. It's a list of functions to determine whether the provided cluster
object needs to be updated according to the desired object and the recorded version.
For reference: https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/util/propagatedversion.go#L30-L59
*/
// objectVersion retrieves the field type-prefixed value used for
// determining currency of the given cluster object.
func objectVersion(clusterObj *unstructured.Unstructured) string {
generation := clusterObj.GetGeneration()
if generation != 0 {
return fmt.Sprintf("%s%d", generationPrefix, generation)
}
return fmt.Sprintf("%s%s", resourceVersionPrefix, clusterObj.GetResourceVersion())
}
// objectNeedsUpdate determines whether the 2 objects provided cluster
// object needs to be updated according to the desired object and the
// recorded version.
func objectNeedsUpdate(desiredObj, clusterObj *unstructured.Unstructured, recordedVersion string) bool {
targetVersion := objectVersion(clusterObj)
if recordedVersion != targetVersion {
return true
}
// If versions match and the version is sourced from the
// generation field, a further check of metadata equivalency is
// required.
return strings.HasPrefix(targetVersion, generationPrefix) && !objectMetaObjEquivalent(desiredObj, clusterObj)
}
// objectMetaObjEquivalent checks if cluster-independent, user provided data in two given ObjectMeta are equal. If in
// the future the ObjectMeta structure is expanded then any field that is not populated
// by the api server should be included here.
func objectMetaObjEquivalent(a, b metav1.Object) bool {
if a.GetName() != b.GetName() {
return false
}
if a.GetNamespace() != b.GetNamespace() {
return false
}
aLabels := a.GetLabels()
bLabels := b.GetLabels()
if !reflect.DeepEqual(aLabels, bLabels) && (len(aLabels) != 0 || len(bLabels) != 0) {
return false
}
return true
return lifted.ObjectNeedsUpdate(desiredObj, clusterObj, version), nil
}

View File

@ -7,7 +7,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/karmada-io/karmada/pkg/util/resourcehelper"
"github.com/karmada-io/karmada/pkg/util/lifted"
)
// Resource is a collection of compute resource.
@ -40,7 +40,7 @@ func NewResource(rl corev1.ResourceList) *Resource {
case corev1.ResourceEphemeralStorage:
r.EphemeralStorage += rQuant.Value()
default:
if resourcehelper.IsScalarResourceName(rName) {
if lifted.IsScalarResourceName(rName) {
r.AddScalar(rName, rQuant.Value())
}
}
@ -65,7 +65,7 @@ func (r *Resource) Add(rl corev1.ResourceList) {
case corev1.ResourceEphemeralStorage:
r.EphemeralStorage += rQuant.Value()
default:
if resourcehelper.IsScalarResourceName(rName) {
if lifted.IsScalarResourceName(rName) {
r.AddScalar(rName, rQuant.Value())
}
}
@ -102,7 +102,7 @@ func (r *Resource) Sub(rl corev1.ResourceList) error {
}
r.EphemeralStorage -= ephemeralStorage
default:
if resourcehelper.IsScalarResourceName(rName) {
if lifted.IsScalarResourceName(rName) {
rScalar, ok := r.ScalarResources[rName]
scalar := rQuant.Value()
if !ok && scalar > 0 {
@ -143,7 +143,7 @@ func (r *Resource) SetMaxResource(rl corev1.ResourceList) {
r.AllowedPodNumber = pods
}
default:
if resourcehelper.IsScalarResourceName(rName) {
if lifted.IsScalarResourceName(rName) {
if value := rQuant.Value(); value > r.ScalarResources[rName] {
r.SetScalar(rName, value)
}
@ -183,7 +183,7 @@ func (r *Resource) ResourceList() corev1.ResourceList {
}
for rName, rQuant := range r.ScalarResources {
if rQuant > 0 {
if resourcehelper.IsHugePageResourceName(rName) {
if lifted.IsHugePageResourceName(rName) {
result[rName] = *resource.NewQuantity(rQuant, resource.BinarySI)
} else {
result[rName] = *resource.NewQuantity(rQuant, resource.DecimalSI)
@ -214,7 +214,7 @@ func (r *Resource) MaxDivided(rl corev1.ResourceList) int64 {
res = MinInt64(res, r.EphemeralStorage/ephemeralStorage)
}
default:
if resourcehelper.IsScalarResourceName(rName) {
if lifted.IsScalarResourceName(rName) {
rScalar := r.ScalarResources[rName]
if scalar := rQuant.Value(); scalar > 0 {
res = MinInt64(res, rScalar/scalar)

View File

@ -1,61 +0,0 @@
package resourcehelper
import (
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
)
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/v1/helper/helpers.go,
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/scheduler/util/utils.go#L144
// IsExtendedResourceName returns true if:
// 1. the resource name is not in the default namespace;
// 2. resource name does not have "requests." prefix,
// to avoid confusion with the convention in quota
// 3. it satisfies the rules in IsQualifiedName() after converted into quota resource name
func IsExtendedResourceName(name corev1.ResourceName) bool {
if IsNativeResource(name) || strings.HasPrefix(string(name), corev1.DefaultResourceRequestsPrefix) {
return false
}
// Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
nameForQuota := fmt.Sprintf("%s%s", corev1.DefaultResourceRequestsPrefix, string(name))
if errs := validation.IsQualifiedName(string(nameForQuota)); len(errs) != 0 {
return false
}
return true
}
// IsPrefixedNativeResource returns true if the resource name is in the
// *kubernetes.io/ namespace.
func IsPrefixedNativeResource(name corev1.ResourceName) bool {
return strings.Contains(string(name), corev1.ResourceDefaultNamespacePrefix)
}
// IsNativeResource returns true if the resource name is in the
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
// implicitly in the kubernetes.io/ namespace.
func IsNativeResource(name corev1.ResourceName) bool {
return !strings.Contains(string(name), "/") ||
IsPrefixedNativeResource(name)
}
// IsHugePageResourceName returns true if the resource name has the huge page
// resource prefix.
func IsHugePageResourceName(name corev1.ResourceName) bool {
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
}
// IsAttachableVolumeResourceName returns true when the resource name is prefixed in attachable volume
func IsAttachableVolumeResourceName(name corev1.ResourceName) bool {
return strings.HasPrefix(string(name), corev1.ResourceAttachableVolumesPrefix)
}
// IsScalarResourceName validates the resource for Extended, Hugepages, Native and AttachableVolume resources
func IsScalarResourceName(name corev1.ResourceName) bool {
return IsExtendedResourceName(name) || IsHugePageResourceName(name) ||
IsPrefixedNativeResource(name) || IsAttachableVolumeResourceName(name)
}

View File

@ -1,16 +1,13 @@
package util
import (
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/lifted"
)
// ResourceMatches tells if the specific resource matches the selector.
@ -83,7 +80,7 @@ func ClusterMatches(cluster *clusterv1alpha1.Cluster, affinity policyv1alpha1.Cl
if affinity.FieldSelector != nil {
var matchFields labels.Selector
var err error
if matchFields, err = nodeSelectorRequirementsAsSelector(affinity.FieldSelector.MatchExpressions); err != nil {
if matchFields, err = lifted.NodeSelectorRequirementsAsSelector(affinity.FieldSelector.MatchExpressions); err != nil {
return false
}
clusterFields := extractClusterFields(cluster)
@ -119,42 +116,6 @@ func ResourceMatchSelectors(resource *unstructured.Unstructured, selectors ...po
return false
}
// This code is directly lifted from the Kubernetes codebase.
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.20/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go#L193-L225
// nodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements
// labels.Selector.
func nodeSelectorRequirementsAsSelector(nsm []corev1.NodeSelectorRequirement) (labels.Selector, error) {
if len(nsm) == 0 {
return labels.Nothing(), nil
}
selector := labels.NewSelector()
for _, expr := range nsm {
var op selection.Operator
switch expr.Operator {
case corev1.NodeSelectorOpIn:
op = selection.In
case corev1.NodeSelectorOpNotIn:
op = selection.NotIn
case corev1.NodeSelectorOpExists:
op = selection.Exists
case corev1.NodeSelectorOpDoesNotExist:
op = selection.DoesNotExist
case corev1.NodeSelectorOpGt:
op = selection.GreaterThan
case corev1.NodeSelectorOpLt:
op = selection.LessThan
default:
return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator)
}
r, err := labels.NewRequirement(expr.Key, op, expr.Values)
if err != nil {
return nil, err
}
selector = selector.Add(*r)
}
return selector, nil
}
func extractClusterFields(cluster *clusterv1alpha1.Cluster) labels.Set {
clusterFieldsMap := make(labels.Set)

View File

@ -13,6 +13,7 @@ import (
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.
@ -149,8 +150,8 @@ func validateResourceList(resourceList corev1.ResourceList, fld *field.Path) fie
for k, v := range resourceList {
resPath := fld.Key(string(k))
errs = append(errs, ValidateResourceQuotaResourceName(string(k), resPath)...)
errs = append(errs, ValidateResourceQuantityValue(string(k), v, resPath)...)
errs = append(errs, lifted.ValidateResourceQuotaResourceName(string(k), resPath)...)
errs = append(errs, lifted.ValidateResourceQuantityValue(string(k), v, resPath)...)
}
return errs