Merge pull request #1460 from XiShanYongYe-Chang/tidy-up-lifted-file
Tidy up lifted file
This commit is contained in:
commit
aef51c0a48
|
@ -10,6 +10,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
api "github.com/karmada-io/karmada/pkg/apis/cluster"
|
api "github.com/karmada-io/karmada/pkg/apis/cluster"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||||
)
|
)
|
||||||
|
|
||||||
const clusterNameMaxLength int = 48
|
const clusterNameMaxLength int = 48
|
||||||
|
@ -86,7 +87,7 @@ func ValidateClusterSpec(spec *api.ClusterSpec, fldPath *field.Path) field.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(spec.Taints) > 0 {
|
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
|
return allErrs
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||||
"github.com/karmada-io/karmada/pkg/util/informermanager"
|
"github.com/karmada-io/karmada/pkg/util/informermanager"
|
||||||
"github.com/karmada-io/karmada/pkg/util/informermanager/keys"
|
"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/names"
|
||||||
"github.com/karmada-io/karmada/pkg/util/ratelimiter"
|
"github.com/karmada-io/karmada/pkg/util/ratelimiter"
|
||||||
"github.com/karmada-io/karmada/pkg/util/restmapper"
|
"github.com/karmada-io/karmada/pkg/util/restmapper"
|
||||||
|
@ -152,7 +153,7 @@ var (
|
||||||
|
|
||||||
func (d *ResourceDetector) discoverResources(period time.Duration) {
|
func (d *ResourceDetector) discoverResources(period time.Duration) {
|
||||||
wait.Until(func() {
|
wait.Until(func() {
|
||||||
newResources := GetDeletableResources(d.DiscoveryClientSet)
|
newResources := lifted.GetDeletableResources(d.DiscoveryClientSet)
|
||||||
for r := range newResources {
|
for r := range newResources {
|
||||||
if d.InformerManager.IsHandlerExist(r, d.EventHandler) || d.gvrDisabled(r) {
|
if d.InformerManager.IsHandlerExist(r, d.EventHandler) || d.gvrDisabled(r) {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"github.com/karmada-io/karmada/pkg/estimator/server/nodes"
|
"github.com/karmada-io/karmada/pkg/estimator/server/nodes"
|
||||||
"github.com/karmada-io/karmada/pkg/util"
|
"github.com/karmada-io/karmada/pkg/util"
|
||||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
"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
|
// NodeMaxAvailableReplica calculates max available replicas of a node, based on
|
||||||
|
|
|
@ -14,9 +14,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
|
||||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
"k8s.io/cli-runtime/pkg/resource"
|
"k8s.io/cli-runtime/pkg/resource"
|
||||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||||
|
@ -25,11 +23,9 @@ import (
|
||||||
karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
|
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/generated/clientset/versioned/scheme"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/options"
|
"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
|
// Exported taint constant strings to mark the type of the current operation
|
||||||
const (
|
const (
|
||||||
MODIFIED = "modified"
|
MODIFIED = "modified"
|
||||||
|
@ -115,7 +111,7 @@ func (o *CommandTaintOption) Complete(args []string) error {
|
||||||
return fmt.Errorf("at least one taint update is required")
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,97 +272,6 @@ func (o *CommandTaintOption) parseTaintArgs(args []string) ([]string, error) {
|
||||||
return taintArgs, nil
|
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,
|
// 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.
|
// 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) {
|
func reorganizeTaints(cluster *clusterv1alpha1.Cluster, overwrite bool, taintsToAdd []corev1.Taint, taintsToRemove []corev1.Taint) (string, []corev1.Taint, error) {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,10 +8,12 @@ import (
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||||
"github.com/karmada-io/karmada/pkg/util"
|
"github.com/karmada-io/karmada/pkg/util"
|
||||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
"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)
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -116,3 +118,21 @@ func getDependenciesFromPodTemplate(podObj *corev1.Pod) ([]configv1alpha1.Depend
|
||||||
|
|
||||||
return dependentObjectRefs, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/karmada-io/karmada/pkg/util"
|
"github.com/karmada-io/karmada/pkg/util"
|
||||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
"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.
|
// 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 {
|
func getAllDefaultRetentionInterpreter() map[schema.GroupVersionKind]retentionInterpreter {
|
||||||
s := make(map[schema.GroupVersionKind]retentionInterpreter)
|
s := make(map[schema.GroupVersionKind]retentionInterpreter)
|
||||||
s[corev1.SchemeGroupVersion.WithKind(util.PodKind)] = retainPodFields
|
s[corev1.SchemeGroupVersion.WithKind(util.PodKind)] = retainPodFields
|
||||||
s[corev1.SchemeGroupVersion.WithKind(util.ServiceKind)] = retainServiceFields
|
s[corev1.SchemeGroupVersion.WithKind(util.ServiceKind)] = lifted.RetainServiceFields
|
||||||
s[corev1.SchemeGroupVersion.WithKind(util.ServiceAccountKind)] = retainServiceAccountFields
|
s[corev1.SchemeGroupVersion.WithKind(util.ServiceAccountKind)] = lifted.RetainServiceAccountFields
|
||||||
s[corev1.SchemeGroupVersion.WithKind(util.PersistentVolumeClaimKind)] = retainPersistentVolumeClaimFields
|
s[corev1.SchemeGroupVersion.WithKind(util.PersistentVolumeClaimKind)] = retainPersistentVolumeClaimFields
|
||||||
s[batchv1.SchemeGroupVersion.WithKind(util.JobKind)] = retainJobSelectorFields
|
s[batchv1.SchemeGroupVersion.WithKind(util.JobKind)] = retainJobSelectorFields
|
||||||
return s
|
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) {
|
func retainPodFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||||
desiredPod, err := helper.ConvertToPod(desired)
|
desiredPod, err := helper.ConvertToPod(desired)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,80 +58,6 @@ func retainPodFields(desired, observed *unstructured.Unstructured) (*unstructure
|
||||||
return unstructuredObj, nil
|
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) {
|
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 is allocated by member cluster and unchangeable, so it should be retained while updating
|
||||||
volumeName, ok, err := unstructured.NestedString(observed.Object, "spec", "volumeName")
|
volumeName, ok, err := unstructured.NestedString(observed.Object, "spec", "volumeName")
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
// However the code has been revised for using Lister instead of API interface.
|
// 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
|
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/controller/deployment/util/deployment_util.go
|
||||||
|
|
||||||
package workload
|
package lifted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
|
@ -1,4 +1,4 @@
|
||||||
package detector
|
package lifted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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.
|
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||||
// For reference:
|
// 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/v1/helper/helpers.go
|
||||||
// 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
|
|
||||||
|
|
||||||
package helper
|
package lifted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -31,12 +29,6 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"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:
|
// IsExtendedResourceName returns true if:
|
||||||
// 1. the resource name is not in the default namespace;
|
// 1. the resource name is not in the default namespace;
|
||||||
// 2. resource name does not have "requests." prefix,
|
// 2. resource name does not have "requests." prefix,
|
||||||
|
@ -54,12 +46,35 @@ func IsExtendedResourceName(name corev1.ResourceName) bool {
|
||||||
return true
|
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
|
// IsNativeResource returns true if the resource name is in the
|
||||||
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
||||||
// implicitly in the kubernetes.io/ namespace.
|
// implicitly in the kubernetes.io/ namespace.
|
||||||
func IsNativeResource(name corev1.ResourceName) bool {
|
func IsNativeResource(name corev1.ResourceName) bool {
|
||||||
return !strings.Contains(string(name), "/") ||
|
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(
|
var standardQuotaResources = sets.NewString(
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package validation
|
package lifted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"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, ";")))
|
allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";")))
|
||||||
}
|
}
|
||||||
// validate the taint effect
|
// 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>
|
// validate if taint is unique by <key, effect>
|
||||||
if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) {
|
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
|
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 {
|
if !allowEmpty && len(*effect) == 0 {
|
||||||
return field.ErrorList{field.Required(fldPath, "")}
|
return field.ErrorList{field.Required(fldPath, "")}
|
||||||
}
|
}
|
|
@ -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
|
|
@ -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#L5073-L5084
|
||||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L5651-L5661
|
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L5651-L5661
|
||||||
|
|
||||||
package federatedresourcequota
|
package lifted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -32,8 +32,6 @@ import (
|
||||||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
"github.com/karmada-io/karmada/pkg/webhook/federatedresourcequota/helper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg
|
const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg
|
||||||
|
@ -46,7 +44,7 @@ func ValidateResourceQuotaResourceName(value string, fldPath *field.Path) field.
|
||||||
allErrs := validateResourceName(value, fldPath)
|
allErrs := validateResourceName(value, fldPath)
|
||||||
|
|
||||||
if len(strings.Split(value, "/")) == 1 {
|
if len(strings.Split(value, "/")) == 1 {
|
||||||
if !helper.IsStandardQuotaResourceName(value) {
|
if !IsStandardQuotaResourceName(value) {
|
||||||
return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource))
|
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 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"))
|
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 {
|
func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
|
allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
|
||||||
if helper.IsIntegerResourceName(resource) {
|
if IsIntegerResourceName(resource) {
|
||||||
if value.MilliValue()%int64(1000) != int64(0) {
|
if value.MilliValue()%int64(1000) != int64(0) {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
|
allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2014 The Kubernetes Authors.
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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.
|
// 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,
|
// For reference:
|
||||||
// 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/api/v1/pod/util.go
|
||||||
// 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
|
|
||||||
//
|
|
||||||
|
|
||||||
package defaultinterpreter
|
package lifted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
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
|
// ContainerType signifies container type
|
||||||
type ContainerType int
|
type ContainerType int
|
||||||
|
|
||||||
|
@ -55,13 +39,24 @@ const (
|
||||||
// AllContainers specifies that all containers be visited
|
// AllContainers specifies that all containers be visited
|
||||||
const AllContainers ContainerType = (InitContainers | Containers | EphemeralContainers)
|
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
|
// ContainerVisitor is called with each container spec, and returns true
|
||||||
// if visiting should continue.
|
// if visiting should continue.
|
||||||
type ContainerVisitor func(container *corev1.Container, containerType ContainerType) (shouldContinue bool)
|
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
|
// 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,
|
// 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,
|
// 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
|
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
|
// VisitPodSecretNames invokes the visitor function with the name of every secret
|
||||||
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
|
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
|
||||||
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSecretNames(pod *corev1.Pod) sets.String {
|
func visitContainerSecretNames(container *corev1.Container, visitor Visitor) bool {
|
||||||
result := sets.NewString()
|
for _, env := range container.EnvFrom {
|
||||||
VisitPodSecretNames(pod, func(name string) bool {
|
if env.SecretRef != nil {
|
||||||
result.Insert(name)
|
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 true
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigMapNames(pod *corev1.Pod) sets.String {
|
// VisitPodConfigmapNames invokes the visitor function with the name of every configmap
|
||||||
result := sets.NewString()
|
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
|
||||||
VisitPodConfigmapNames(pod, func(name string) bool {
|
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
|
||||||
result.Insert(name)
|
// Returns true if visiting completed, false if visiting was short-circuited.
|
||||||
return true
|
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
|
||||||
}
|
}
|
|
@ -3,8 +3,6 @@ package objectwatcher
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
@ -19,14 +17,10 @@ import (
|
||||||
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||||
"github.com/karmada-io/karmada/pkg/resourceinterpreter"
|
"github.com/karmada-io/karmada/pkg/resourceinterpreter"
|
||||||
"github.com/karmada-io/karmada/pkg/util"
|
"github.com/karmada-io/karmada/pkg/util"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||||
"github.com/karmada-io/karmada/pkg/util/restmapper"
|
"github.com/karmada-io/karmada/pkg/util/restmapper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
generationPrefix = "gen:"
|
|
||||||
resourceVersionPrefix = "rv:"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ObjectWatcher manages operations for object dispatched to member clusters.
|
// ObjectWatcher manages operations for object dispatched to member clusters.
|
||||||
type ObjectWatcher interface {
|
type ObjectWatcher interface {
|
||||||
Create(clusterName string, desireObj *unstructured.Unstructured) error
|
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
|
// recordVersion will add or update resource version records
|
||||||
func (o *objectWatcherImpl) recordVersion(clusterObj *unstructured.Unstructured, clusterName string) {
|
func (o *objectWatcherImpl) recordVersion(clusterObj *unstructured.Unstructured, clusterName string) {
|
||||||
objVersion := objectVersion(clusterObj)
|
objVersion := lifted.ObjectVersion(clusterObj)
|
||||||
objectKey := o.genObjectKey(clusterObj)
|
objectKey := o.genObjectKey(clusterObj)
|
||||||
if o.isClusterVersionRecordExist(clusterName) {
|
if o.isClusterVersionRecordExist(clusterName) {
|
||||||
o.updateVersionRecord(clusterName, objectKey, objVersion)
|
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 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
|
return lifted.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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"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.
|
// Resource is a collection of compute resource.
|
||||||
|
@ -40,7 +40,7 @@ func NewResource(rl corev1.ResourceList) *Resource {
|
||||||
case corev1.ResourceEphemeralStorage:
|
case corev1.ResourceEphemeralStorage:
|
||||||
r.EphemeralStorage += rQuant.Value()
|
r.EphemeralStorage += rQuant.Value()
|
||||||
default:
|
default:
|
||||||
if resourcehelper.IsScalarResourceName(rName) {
|
if lifted.IsScalarResourceName(rName) {
|
||||||
r.AddScalar(rName, rQuant.Value())
|
r.AddScalar(rName, rQuant.Value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func (r *Resource) Add(rl corev1.ResourceList) {
|
||||||
case corev1.ResourceEphemeralStorage:
|
case corev1.ResourceEphemeralStorage:
|
||||||
r.EphemeralStorage += rQuant.Value()
|
r.EphemeralStorage += rQuant.Value()
|
||||||
default:
|
default:
|
||||||
if resourcehelper.IsScalarResourceName(rName) {
|
if lifted.IsScalarResourceName(rName) {
|
||||||
r.AddScalar(rName, rQuant.Value())
|
r.AddScalar(rName, rQuant.Value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ func (r *Resource) Sub(rl corev1.ResourceList) error {
|
||||||
}
|
}
|
||||||
r.EphemeralStorage -= ephemeralStorage
|
r.EphemeralStorage -= ephemeralStorage
|
||||||
default:
|
default:
|
||||||
if resourcehelper.IsScalarResourceName(rName) {
|
if lifted.IsScalarResourceName(rName) {
|
||||||
rScalar, ok := r.ScalarResources[rName]
|
rScalar, ok := r.ScalarResources[rName]
|
||||||
scalar := rQuant.Value()
|
scalar := rQuant.Value()
|
||||||
if !ok && scalar > 0 {
|
if !ok && scalar > 0 {
|
||||||
|
@ -143,7 +143,7 @@ func (r *Resource) SetMaxResource(rl corev1.ResourceList) {
|
||||||
r.AllowedPodNumber = pods
|
r.AllowedPodNumber = pods
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if resourcehelper.IsScalarResourceName(rName) {
|
if lifted.IsScalarResourceName(rName) {
|
||||||
if value := rQuant.Value(); value > r.ScalarResources[rName] {
|
if value := rQuant.Value(); value > r.ScalarResources[rName] {
|
||||||
r.SetScalar(rName, value)
|
r.SetScalar(rName, value)
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ func (r *Resource) ResourceList() corev1.ResourceList {
|
||||||
}
|
}
|
||||||
for rName, rQuant := range r.ScalarResources {
|
for rName, rQuant := range r.ScalarResources {
|
||||||
if rQuant > 0 {
|
if rQuant > 0 {
|
||||||
if resourcehelper.IsHugePageResourceName(rName) {
|
if lifted.IsHugePageResourceName(rName) {
|
||||||
result[rName] = *resource.NewQuantity(rQuant, resource.BinarySI)
|
result[rName] = *resource.NewQuantity(rQuant, resource.BinarySI)
|
||||||
} else {
|
} else {
|
||||||
result[rName] = *resource.NewQuantity(rQuant, resource.DecimalSI)
|
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)
|
res = MinInt64(res, r.EphemeralStorage/ephemeralStorage)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if resourcehelper.IsScalarResourceName(rName) {
|
if lifted.IsScalarResourceName(rName) {
|
||||||
rScalar := r.ScalarResources[rName]
|
rScalar := r.ScalarResources[rName]
|
||||||
if scalar := rQuant.Value(); scalar > 0 {
|
if scalar := rQuant.Value(); scalar > 0 {
|
||||||
res = MinInt64(res, rScalar/scalar)
|
res = MinInt64(res, rScalar/scalar)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,16 +1,13 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
|
||||||
|
|
||||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/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.
|
// 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 {
|
if affinity.FieldSelector != nil {
|
||||||
var matchFields labels.Selector
|
var matchFields labels.Selector
|
||||||
var err error
|
var err error
|
||||||
if matchFields, err = nodeSelectorRequirementsAsSelector(affinity.FieldSelector.MatchExpressions); err != nil {
|
if matchFields, err = lifted.NodeSelectorRequirementsAsSelector(affinity.FieldSelector.MatchExpressions); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
clusterFields := extractClusterFields(cluster)
|
clusterFields := extractClusterFields(cluster)
|
||||||
|
@ -119,42 +116,6 @@ func ResourceMatchSelectors(resource *unstructured.Unstructured, selectors ...po
|
||||||
return false
|
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 {
|
func extractClusterFields(cluster *clusterv1alpha1.Cluster) labels.Set {
|
||||||
clusterFieldsMap := make(labels.Set)
|
clusterFieldsMap := make(labels.Set)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
clustervalidation "github.com/karmada-io/karmada/pkg/apis/cluster/validation"
|
clustervalidation "github.com/karmada-io/karmada/pkg/apis/cluster/validation"
|
||||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
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.
|
// 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 {
|
for k, v := range resourceList {
|
||||||
resPath := fld.Key(string(k))
|
resPath := fld.Key(string(k))
|
||||||
errs = append(errs, ValidateResourceQuotaResourceName(string(k), resPath)...)
|
errs = append(errs, lifted.ValidateResourceQuotaResourceName(string(k), resPath)...)
|
||||||
errs = append(errs, ValidateResourceQuantityValue(string(k), v, resPath)...)
|
errs = append(errs, lifted.ValidateResourceQuantityValue(string(k), v, resPath)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
|
|
Loading…
Reference in New Issue